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); }
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; } }
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)); }
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(); } } }
// 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); }
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)); } }
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(); } } }
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); }
public Task <CompletionChange> GetCompletionChangeAsync(TextSpan completionSpan, CompletionItem item, CancellationToken cancellationToken) { return(_completionService.GetChangeAsync(Document, item, cancellationToken: cancellationToken)); }