예제 #1
0
                static async Task <LSP.TextEdit> GenerateTextEdit(
                    Document document,
                    CompletionItem item,
                    CompletionService completionService,
                    SourceText?documentText,
                    TextSpan?defaultSpan,
                    LSP.Range?defaultRange,
                    CancellationToken cancellationToken)
                {
                    Contract.ThrowIfNull(documentText);
                    Contract.ThrowIfNull(defaultSpan);
                    Contract.ThrowIfNull(defaultRange);

                    var completionChange = await completionService.GetChangeAsync(
                        document, item, cancellationToken : cancellationToken).ConfigureAwait(false);

                    var completionChangeSpan = completionChange.TextChange.Span;

                    var textEdit = new LSP.TextEdit()
                    {
                        NewText = completionChange.TextChange.NewText ?? "",
                        Range   = completionChangeSpan == defaultSpan.Value
                            ? defaultRange
                            : ProtocolConversions.TextSpanToRange(completionChangeSpan, documentText),
                    };

                    return(textEdit);
                }
예제 #2
0
                static async Task AddTextEdit(
                    Document document,
                    CompletionItem item,
                    LSP.CompletionItem lspItem,
                    CompletionService completionService,
                    SourceText documentText,
                    TextSpan defaultSpan,
                    bool itemDefaultsSupported,
                    CancellationToken cancellationToken)
                {
                    var completionChange = await completionService.GetChangeAsync(
                        document, item, cancellationToken : cancellationToken).ConfigureAwait(false);

                    var completionChangeSpan = completionChange.TextChange.Span;
                    var newText = completionChange.TextChange.NewText ?? "";

                    if (itemDefaultsSupported && completionChangeSpan == defaultSpan)
                    {
                        // The span is the same as the default, we just need to store the new text as
                        // the insert text so the client can create the text edit from it and the default range.
                        lspItem.InsertText = newText;
                    }
                    else
                    {
                        var textEdit = new LSP.TextEdit()
                        {
                            NewText = newText,
                            Range   = ProtocolConversions.TextSpanToRange(completionChangeSpan, documentText),
                        };
                        lspItem.TextEdit = textEdit;
                    }
                }
예제 #3
0
        public override void InsertCompletionText(CompletionListWindow window, ref KeyActions ka, KeyDescriptor descriptor)
        {
            var document = IdeApp.Workbench.ActiveDocument;
            var editor   = document?.Editor;

            if (editor == null || completionService == null)
            {
                base.InsertCompletionText(window, ref ka, descriptor);
                return;
            }
            var completionChange = completionService.GetChangeAsync(doc, CompletionItem, null, default(CancellationToken)).WaitAndGetResult(default(CancellationToken));

            var currentBuffer = editor.GetPlatformTextBuffer();
            var textChange    = completionChange.TextChange;

            var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, new Span(textChange.Span.Start, textChange.Span.Length));

            var mappedSpan = triggerSnapshotSpan.TranslateTo(triggerSnapshot.TextBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);

            triggerSnapshot.TextBuffer.Replace(mappedSpan, completionChange.TextChange.NewText);
//			editor.ReplaceText (mappedSpan.Start, mappedSpan.Length, completionChange.TextChange.NewText);

            if (completionChange.NewPosition.HasValue)
            {
                editor.CaretOffset = completionChange.NewPosition.Value;
            }

            if (CompletionItem.Rules.FormatOnCommit)
            {
                var endOffset = mappedSpan.Start + completionChange.TextChange.NewText.Length;
                Format(editor, document, mappedSpan.Start, endOffset);
            }
        }
        /// <summary>
        /// Internal for testing purposes only.
        /// </summary>
        internal static async Task <TextChange> GetTextChangeAsync(
            CompletionService service, Document document, CompletionItem item,
            char?commitKey = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            var change = await service.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false);

            // normally the items that produce multiple changes are not expecting to trigger the behaviors that rely on looking at the text
            return(change.TextChange);
        }
        public Task <CompletionChange?> GetCompletionChangeAsync(CompletionItem item)
        {
            if (CurrentDocument != null)
            {
                CompletionService completionService = CompletionService.GetService(CurrentDocument);
                return(completionService.GetChangeAsync(CurrentDocument, item));
            }

            return(Task.FromResult <CompletionChange?>(null));
        }
예제 #6
0
        public override void Commit()
        {
            var completion = SelectionStatus.Completion as RoslynCompletion;

            if (completion == null)
            {
                base.Commit();
                return;
            }

            mruCompletionService.AddText(completion.DisplayText);

            var info = CompletionInfo.Create(ApplicableTo.TextBuffer.CurrentSnapshot);

            Debug.Assert(info != null);
            if (info == null)
            {
                return;
            }

            var change          = completionService.GetChangeAsync(info.Value.Document, completion.CompletionItem, commitCharacter: null).GetAwaiter().GetResult();
            var buffer          = ApplicableTo.TextBuffer;
            var currentSnapshot = buffer.CurrentSnapshot;

            using (var ed = buffer.CreateEdit()) {
                foreach (var c in change.TextChanges)
                {
                    Debug.Assert(c.Span.End <= originalSnapshot.Length);
                    if (c.Span.End > originalSnapshot.Length)
                    {
                        return;
                    }
                    var span = new SnapshotSpan(originalSnapshot, c.Span.ToSpan()).TranslateTo(currentSnapshot, SpanTrackingMode.EdgeInclusive);
                    if (!ed.Replace(span.Span, c.NewText))
                    {
                        return;
                    }
                }
                ed.Apply();
            }
            if (change.NewPosition != null)
            {
                var snapshot = buffer.CurrentSnapshot;
                Debug.Assert(change.NewPosition.Value <= snapshot.Length);
                if (change.NewPosition.Value <= snapshot.Length)
                {
                    textView.Caret.MoveTo(new SnapshotPoint(snapshot, change.NewPosition.Value));
                    textView.Caret.EnsureVisible();
                }
            }
        }
예제 #7
0
        // Internal for testing
        internal static async Task <LSP.TextEdit> GenerateTextEditAsync(
            Document document,
            CompletionService completionService,
            CompletionItem selectedItem,
            bool snippetsSupported,
            CancellationToken cancellationToken
            )
        {
            var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var completionChange = await completionService
                                   .GetChangeAsync(document, selectedItem, cancellationToken : cancellationToken)
                                   .ConfigureAwait(false);

            var completionChangeSpan = completionChange.TextChange.Span;
            var newText = completionChange.TextChange.NewText;

            Contract.ThrowIfNull(newText);

            // If snippets are supported, that means we can move the caret (represented by $0) to
            // a new location.
            if (snippetsSupported)
            {
                var caretPosition = completionChange.NewPosition;
                if (caretPosition.HasValue)
                {
                    // caretPosition is the absolute position of the caret in the document.
                    // We want the position relative to the start of the snippet.
                    var relativeCaretPosition = caretPosition.Value - completionChangeSpan.Start;

                    // The caret could technically be placed outside the bounds of the text
                    // being inserted. This situation is currently unsupported in LSP, so in
                    // these cases we won't move the caret.
                    if (relativeCaretPosition >= 0 && relativeCaretPosition <= newText.Length)
                    {
                        newText = newText.Insert(relativeCaretPosition, "$0");
                    }
                }
            }

            var textEdit = new LSP.TextEdit()
            {
                NewText = newText,
                Range   = ProtocolConversions.TextSpanToRange(completionChangeSpan, documentText),
            };

            return(textEdit);
        }
예제 #8
0
        public static async Task <TextChange> GetTextChangeAsync(
            CompletionService service, Document document, CompletionItem item,
            char?commitKey = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            var change = await service.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false);

            // normally the items that produce multiple changes are not expecting to trigger the behaviors that rely on looking at the text
            if (change.TextChanges.Length == 1)
            {
                return(change.TextChanges[0]);
            }
            else
            {
                return(new TextChange(item.Span, item.DisplayText));
            }
        }
예제 #9
0
        public void Commit(RoslynCompletion completion)
        {
            if (completion is null)
            {
                throw new ArgumentNullException(nameof(completion));
            }

            mruCompletionService.AddText(completion.DisplayText);

            var info = CompletionInfo.Create(ApplicableTo.TextBuffer.CurrentSnapshot);

            Debug2.Assert(!(info is null));
            if (info is null)
            {
                return;
            }

            var change          = completionService.GetChangeAsync(info.Value.Document, completion.CompletionItem, commitCharacter: null).GetAwaiter().GetResult();
            var buffer          = ApplicableTo.TextBuffer;
            var currentSnapshot = buffer.CurrentSnapshot;

            using (var ed = buffer.CreateEdit()) {
                var textChange = change.TextChange;
                Debug.Assert(textChange.Span.End <= originalSnapshot.Length);
                if (textChange.Span.End > originalSnapshot.Length)
                {
                    return;
                }
                var span = new SnapshotSpan(originalSnapshot, textChange.Span.ToSpan()).TranslateTo(currentSnapshot, SpanTrackingMode.EdgeInclusive);
                if (!ed.Replace(span.Span, textChange.NewText))
                {
                    return;
                }
                ed.Apply();
            }
            if (!(change.NewPosition is null))
            {
                var snapshot = buffer.CurrentSnapshot;
                Debug.Assert(change.NewPosition.Value <= snapshot.Length);
                if (change.NewPosition.Value <= snapshot.Length)
                {
                    textView.Caret.MoveTo(new SnapshotPoint(snapshot, change.NewPosition.Value));
                    textView.Caret.EnsureVisible();
                }
            }
        }
예제 #10
0
        private AsyncCompletionData.CommitBehavior Commit(
            Document document,
            CompletionService completionService,
            ITextView view,
            ITextBuffer subjectBuffer,
            RoslynCompletionItem roslynItem,
            TextSpan completionListSpan,
            char?commitCharacter,
            ITextSnapshot triggerSnapshot,
            CompletionRules rules,
            string filterText,
            CancellationToken cancellationToken)
        {
            AssertIsForeground();

            bool includesCommitCharacter;

            if (!subjectBuffer.CheckEditAccess())
            {
                // We are on the wrong thread.
                FatalError.ReportWithoutCrash(new InvalidOperationException("Subject buffer did not provide Edit Access"));
                return(AsyncCompletionData.CommitBehavior.None);
            }

            if (subjectBuffer.EditInProgress)
            {
                FatalError.ReportWithoutCrash(new InvalidOperationException("Subject buffer is editing by someone else."));
                return(AsyncCompletionData.CommitBehavior.None);
            }

            var change = completionService.GetChangeAsync(
                document, roslynItem, completionListSpan, commitCharacter, cancellationToken).WaitAndGetResult(cancellationToken);
            var textChange          = change.TextChange;
            var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, textChange.Span.ToSpan());
            var mappedSpan          = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);

            using (var edit = subjectBuffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null))
            {
                edit.Replace(mappedSpan.Span, change.TextChange.NewText);

                // edit.Apply() may trigger changes made by extensions.
                // updatedCurrentSnapshot will contain changes made by Roslyn but not by other extensions.
                var updatedCurrentSnapshot = edit.Apply();

                if (change.NewPosition.HasValue)
                {
                    // Roslyn knows how to positionate the caret in the snapshot we just created.
                    // If there were more edits made by extensions, TryMoveCaretToAndEnsureVisible maps the snapshot point to the most recent one.
                    view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(updatedCurrentSnapshot, change.NewPosition.Value));
                }
                else
                {
                    // Or, If we're doing a minimal change, then the edit that we make to the
                    // buffer may not make the total text change that places the caret where we
                    // would expect it to go based on the requested change. In this case,
                    // determine where the item should go and set the care manually.

                    // Note: we only want to move the caret if the caret would have been moved
                    // by the edit.  i.e. if the caret was actually in the mapped span that
                    // we're replacing.
                    var caretPositionInBuffer = view.GetCaretPoint(subjectBuffer);
                    if (caretPositionInBuffer.HasValue && mappedSpan.IntersectsWith(caretPositionInBuffer.Value))
                    {
                        view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText.Length));
                    }
                    else
                    {
                        view.Caret.EnsureVisible();
                    }
                }

                includesCommitCharacter = change.IncludesCommitCharacter;

                if (roslynItem.Rules.FormatOnCommit)
                {
                    // The edit updates the snapshot however other extensions may make changes there.
                    // Therefore, it is required to use subjectBuffer.CurrentSnapshot for further calculations rather than the updated current snapsot defined above.
                    document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
                    var spanToFormat      = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);
                    var formattingService = document?.GetLanguageService <IEditorFormattingService>();

                    if (formattingService != null)
                    {
                        var changes = formattingService.GetFormattingChangesAsync(
                            document, spanToFormat.Span.ToTextSpan(), CancellationToken.None).WaitAndGetResult(CancellationToken.None);
                        document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, CancellationToken.None);
                    }
                }
            }

            if (includesCommitCharacter)
            {
                return(AsyncCompletionData.CommitBehavior.SuppressFurtherTypeCharCommandHandlers);
            }

            if (commitCharacter == '\n' && SendEnterThroughToEditor(rules, roslynItem, filterText))
            {
                return(AsyncCompletionData.CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers);
            }

            return(AsyncCompletionData.CommitBehavior.None);
        }
예제 #11
0
 public Task <CompletionChange> GetCompletionChangeAsync(TextSpan completionSpan, CompletionItem item, CancellationToken cancellationToken)
 {
     return(_completionService.GetChangeAsync(Document, item, cancellationToken: cancellationToken));
 }