public void ExecuteCommand(PasteCommandArgs args, Action nextHandler, CommandExecutionContext context) { HandlePossibleTypingCommand(args, nextHandler, span => { nextHandler(); }); }
public void ExecuteCommand(PasteCommandArgs args, Action nextHandler, CommandExecutionContext context) { using var _ = context.OperationContext.AddScope(allowCancellation: true, EditorFeaturesResources.Formatting_pasted_text); var caretPosition = args.TextView.GetCaretPoint(args.SubjectBuffer); nextHandler(); var cancellationToken = context.OperationContext.UserCancellationToken; if (cancellationToken.IsCancellationRequested) { return; } try { ExecuteCommandWorker(args, caretPosition, cancellationToken); } catch (OperationCanceledException) { // According to Editor command handler API guidelines, it's best if we return early if cancellation // is requested instead of throwing. Otherwise, we could end up in an invalid state due to already // calling nextHandler(). } }
public void ExecuteCommand(PasteCommandArgs args, Action nextHandler, CommandExecutionContext context) { using (context.OperationContext.AddScope(allowCancellation: true, EditorFeaturesResources.Formatting_pasted_text)) { ExecuteCommandWorker(args, nextHandler, context.OperationContext.UserCancellationToken); } }
public void ExecuteCommand(PasteCommandArgs args, Action nextHandler) { _waitIndicator.Wait( title: EditorFeaturesResources.Format_Paste, message: EditorFeaturesResources.Formatting_pasted_text, allowCancel: true, action: c => ExecuteCommandWorker(args, nextHandler, c.CancellationToken)); }
private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint?caretPosition, CancellationToken cancellationToken) { if (!caretPosition.HasValue) { return; } var subjectBuffer = args.SubjectBuffer; var document = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) { return; } if (!_globalOptions.GetOption(FormattingOptionsMetadata.FormatOnPaste, document.Project.Language)) { return; } var solution = document.Project.Solution; if (!solution.Workspace.CanApplyChange(ApplyChangesKind.ChangeDocument)) { return; } var services = solution.Workspace.Services; var formattingRuleService = services.GetService <IHostDependentFormattingRuleFactoryService>(); if (formattingRuleService != null && formattingRuleService.ShouldNotFormatOrCommitOnPaste(document.Id)) { return; } var formattingService = document.GetLanguageService <IFormattingInteractionService>(); if (formattingService == null || !formattingService.SupportsFormatOnPaste) { return; } var trackingSpan = caretPosition.Value.Snapshot.CreateTrackingSpan(caretPosition.Value.Position, 0, SpanTrackingMode.EdgeInclusive); var span = trackingSpan.GetSpan(subjectBuffer.CurrentSnapshot).Span.ToTextSpan(); // Note: C# always completes synchronously, TypeScript is async var changes = formattingService.GetFormattingChangesOnPasteAsync(document, subjectBuffer, span, cancellationToken).WaitAndGetResult(cancellationToken); if (changes.IsEmpty) { return; } subjectBuffer.ApplyChanges(changes); }
private static void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint?caretPosition, CancellationToken cancellationToken) { if (!caretPosition.HasValue) { return; } var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) { return; } var solution = document.Project.Solution; if (!solution.Options.GetOption(FormattingBehaviorOptions.FormatOnPaste, document.Project.Language)) { return; } if (!solution.Workspace.CanApplyChange(ApplyChangesKind.ChangeDocument)) { return; } var formattingRuleService = solution.Workspace.Services.GetService <IHostDependentFormattingRuleFactoryService>(); if (formattingRuleService != null && formattingRuleService.ShouldNotFormatOrCommitOnPaste(document)) { return; } var formattingService = document.GetLanguageService <IFormattingInteractionService>(); if (formattingService == null || !formattingService.SupportsFormatOnPaste) { return; } var trackingSpan = caretPosition.Value.Snapshot.CreateTrackingSpan(caretPosition.Value.Position, 0, SpanTrackingMode.EdgeInclusive); var span = trackingSpan.GetSpan(args.SubjectBuffer.CurrentSnapshot).Span.ToTextSpan(); var changes = formattingService.GetFormattingChangesOnPasteAsync( document, span, documentOptions: null, cancellationToken).WaitAndGetResult(cancellationToken); if (changes.IsEmpty) { return; } solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken); }
private void ExecuteCommandWorker(PasteCommandArgs args, Action nextHandler, CancellationToken cancellationToken) { var caretPosition = args.TextView.GetCaretPoint(args.SubjectBuffer); nextHandler(); if (!args.SubjectBuffer.CanApplyChangeDocumentToWorkspace()) { return; } if (!args.SubjectBuffer.GetFeatureOnOffOption(FeatureOnOffOptions.FormatOnPaste) || !caretPosition.HasValue) { return; } var trackingSpan = caretPosition.Value.Snapshot.CreateTrackingSpan(caretPosition.Value.Position, 0, SpanTrackingMode.EdgeInclusive); var document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) { return; } var formattingRuleService = document.Project.Solution.Workspace.Services.GetService <IHostDependentFormattingRuleFactoryService>(); if (formattingRuleService != null && formattingRuleService.ShouldNotFormatOrCommitOnPaste(document)) { return; } var formattingService = document.GetLanguageService <IEditorFormattingService>(); if (formattingService == null || !formattingService.SupportsFormatOnPaste) { return; } var span = trackingSpan.GetSpan(args.SubjectBuffer.CurrentSnapshot).Span.ToTextSpan(); var changes = formattingService.GetFormattingChangesOnPasteAsync(document, span, cancellationToken).WaitAndGetResult(cancellationToken); if (changes.Count == 0) { return; } document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, cancellationToken); }
protected static void AssertFormatWithPasteOrReturn(string expectedWithMarker, string codeWithMarker, bool allowDocumentChanges, bool isPaste = true) { using (var workspace = CSharpWorkspaceFactory.CreateWorkspaceFromLines(codeWithMarker)) { workspace.CanApplyChangeDocument = allowDocumentChanges; // set up caret position var testDocument = workspace.Documents.Single(); var view = testDocument.GetTextView(); view.Caret.MoveTo(new SnapshotPoint(view.TextSnapshot, testDocument.CursorPosition.Value)); // get original buffer var buffer = workspace.Documents.First().GetTextBuffer(); var optionService = workspace.Services.GetService <IOptionService>(); if (isPaste) { optionService.SetOptions(optionService.GetOptions().WithChangedOption(FeatureOnOffOptions.FormatOnPaste, LanguageNames.CSharp, true)); var commandHandler = new FormatCommandHandler(TestWaitIndicator.Default, null, null); var commandArgs = new PasteCommandArgs(view, view.TextBuffer); commandHandler.ExecuteCommand(commandArgs, () => { }); } else { // Return Key Command var textUndoHistory = new Mock <ITextUndoHistoryRegistry>(); var editorOperationsFactory = new Mock <IEditorOperationsFactoryService>(); var editorOperations = new Mock <IEditorOperations>(); editorOperationsFactory.Setup(x => x.GetEditorOperations(testDocument.GetTextView())).Returns(editorOperations.Object); var commandHandler = new FormatCommandHandler(TestWaitIndicator.Default, textUndoHistory.Object, editorOperationsFactory.Object); var commandArgs = new ReturnKeyCommandArgs(view, view.TextBuffer); commandHandler.ExecuteCommand(commandArgs, () => { }); } string expected; int expectedPosition; MarkupTestFile.GetPosition(expectedWithMarker, out expected, out expectedPosition); Assert.Equal(expected, view.TextSnapshot.GetText()); var caretPosition = view.Caret.Position.BufferPosition.Position; Assert.True(expectedPosition == caretPosition, string.Format("Caret positioned incorrectly. Should have been {0}, but was {1}.", expectedPosition, caretPosition)); } }
protected static void AssertFormatWithPasteOrReturn(string expectedWithMarker, string codeWithMarker, bool allowDocumentChanges, bool isPaste = true) { using (var workspace = TestWorkspace.CreateCSharp(codeWithMarker)) { workspace.CanApplyChangeDocument = allowDocumentChanges; // set up caret position var testDocument = workspace.Documents.Single(); var view = testDocument.GetTextView(); view.Caret.MoveTo(new SnapshotPoint(view.TextSnapshot, testDocument.CursorPosition.Value)); // get original buffer var buffer = workspace.Documents.First().GetTextBuffer(); if (isPaste) { var commandHandler = workspace.GetService <FormatCommandHandler>(); var commandArgs = new PasteCommandArgs(view, view.TextBuffer); commandHandler.ExecuteCommand(commandArgs, () => { }, TestCommandExecutionContext.Create()); } else { // Return Key Command var commandHandler = workspace.GetService <FormatCommandHandler>(); var commandArgs = new ReturnKeyCommandArgs(view, view.TextBuffer); commandHandler.ExecuteCommand(commandArgs, () => { }, TestCommandExecutionContext.Create()); } MarkupTestFile.GetPosition(expectedWithMarker, out var expected, out int expectedPosition); Assert.Equal(expected, view.TextSnapshot.GetText()); var caretPosition = view.Caret.Position.BufferPosition.Position; Assert.True(expectedPosition == caretPosition, string.Format("Caret positioned incorrectly. Should have been {0}, but was {1}.", expectedPosition, caretPosition)); } }
void IChainedCommandHandler <PasteCommandArgs> .ExecuteCommand(PasteCommandArgs args, Action nextHandler, CommandExecutionContext context) { AssertIsForeground(); ExecuteCommandWorker(args, nextHandler, context); }
public CommandState GetCommandState(PasteCommandArgs args, Func <CommandState> nextHandler) { return(nextHandler()); }
CommandState ICommandHandler <PasteCommandArgs> .GetCommandState(PasteCommandArgs args) { return(CommandState.Available); }
void ICommandHandler <PasteCommandArgs> .ExecuteCommand(PasteCommandArgs args, Action nextHandler) { AssertIsForeground(); EnsureCompletionSessionStopped(); nextHandler(); }
bool ICommandHandler <PasteCommandArgs> .ExecuteCommand(PasteCommandArgs args, CommandExecutionContext executionContext) { GetOperations(args.TextView).Paste(); return(true); }
public CommandState GetCommandState(PasteCommandArgs args, Func <CommandState> nextHandler) => nextHandler();
void ICommandHandler <PasteCommandArgs> .ExecuteCommand(PasteCommandArgs args, Action nextHandler) { AssertIsForeground(); DismissSessionIfActive(); nextHandler(); }
VSCommanding.CommandState IChainedCommandHandler <PasteCommandArgs> .GetCommandState(PasteCommandArgs args, System.Func <VSCommanding.CommandState> nextHandler) { AssertIsForeground(); return(nextHandler()); }
void ICommandHandler <PasteCommandArgs> .ExecuteCommand(PasteCommandArgs args, Action nextHandler) { AssertIsForeground(); ExecuteCommandWorker(args, nextHandler); }
CommandState ICommandHandler <PasteCommandArgs> .GetCommandState(PasteCommandArgs args, Func <CommandState> nextHandler) { AssertIsForeground(); return(GetCommandStateWorker(args, nextHandler)); }
void IChainedCommandHandler <PasteCommandArgs> .ExecuteCommand(PasteCommandArgs args, Action nextHandler, CommandExecutionContext context) { AssertIsForeground(); DismissSessionIfActive(); nextHandler(); }
public void ExecuteCommand(PasteCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext) { Contract.ThrowIfFalse(_threadingContext.HasMainThread); var textView = args.TextView; var subjectBuffer = args.SubjectBuffer; var selectionsBeforePaste = textView.Selection.GetSnapshotSpansOnBuffer(subjectBuffer); var snapshotBeforePaste = subjectBuffer.CurrentSnapshot; // Always let the real paste go through. That way we always have a version of the document that doesn't // include our changes that we can undo back to. nextCommandHandler(); // If we don't even see any changes from the paste, there's nothing we can do. if (snapshotBeforePaste.Version.Changes is null) { return; } // If the user has the option off, then don't bother doing anything once we've sent the paste through. if (!_globalOptions.GetOption(FeatureOnOffOptions.AutomaticallyFixStringContentsOnPaste, LanguageNames.CSharp)) { return; } // if we're not even sure where the user caret/selection is on this buffer, we can't proceed. if (selectionsBeforePaste.Count == 0) { return; } var snapshotAfterPaste = subjectBuffer.CurrentSnapshot; // If there were multiple changes that already happened, then don't make any changes. Some other component // already did something advanced. if (snapshotAfterPaste.Version != snapshotBeforePaste.Version.Next) { return; } // Have to even be in a C# doc to be able to have special space processing here. var documentBeforePaste = snapshotBeforePaste.GetOpenDocumentInCurrentContextWithChanges(); var documentAfterPaste = snapshotAfterPaste.GetOpenDocumentInCurrentContextWithChanges(); if (documentBeforePaste == null || documentAfterPaste == null) { return; } var cancellationToken = executionContext.OperationContext.UserCancellationToken; var parsedDocumentBeforePaste = ParsedDocument.CreateSynchronously(documentBeforePaste, cancellationToken); // When pasting, only do anything special if the user selections were entirely inside a single string // token/expression. Otherwise, we have a multi-selection across token kinds which will be extremely // complex to try to reconcile. var stringExpressionBeforePaste = TryGetCompatibleContainingStringExpression(parsedDocumentBeforePaste, selectionsBeforePaste); if (stringExpressionBeforePaste == null) { return; } // Also ensure that all the changes the editor actually applied were inside a single string // token/expression. If the editor decided to make changes outside of the string, we definitely do not want // to do anything here. var stringExpressionBeforePasteFromChanges = TryGetCompatibleContainingStringExpression( parsedDocumentBeforePaste, new NormalizedSnapshotSpanCollection(snapshotBeforePaste, snapshotBeforePaste.Version.Changes.Select(c => c.OldSpan))); if (stringExpressionBeforePaste != stringExpressionBeforePasteFromChanges) { return; } var textChanges = GetEdits(cancellationToken); // If we didn't get any viable changes back, don't do anything. if (textChanges.IsDefaultOrEmpty) { return; } var newTextAfterChanges = snapshotBeforePaste.AsText().WithChanges(textChanges); // If we end up making the same changes as what the paste did, then no need to proceed. if (ContentsAreSame(snapshotBeforePaste, snapshotAfterPaste, stringExpressionBeforePaste, newTextAfterChanges)) { return; } // Create two edits to make the change. The first restores the buffer to the original snapshot (effectively // undoing the first set of changes). Then the second actually applies the change. // // Do this as direct edits, passing 'EditOptions.None' for the options, as we want to control the edits // precisely and don't want any strange interpretation of where the caret should end up. Other options // (like DefaultMinimalChange) will attempt to diff/merge edits oddly sometimes which can lead the caret // ending up before/after some merged change, which will no longer match the behavior of precise pastes. // // Wrap this all as a transaction so that these two edits appear to be one single change. This also allows // the user to do a single 'undo' that gets them back to the original paste made at the start of this // method. using var transaction = new CaretPreservingEditTransaction( CSharpEditorResources.Fixing_string_literal_after_paste, textView, _undoHistoryRegistry, _editorOperationsFactoryService); { var edit = subjectBuffer.CreateEdit(EditOptions.None, reiteratedVersionNumber: null, editTag: null); foreach (var change in snapshotBeforePaste.Version.Changes) { edit.Replace(change.NewSpan, change.OldText); } edit.Apply(); } { var edit = subjectBuffer.CreateEdit(EditOptions.None, reiteratedVersionNumber: null, editTag: null); foreach (var selection in selectionsBeforePaste) { edit.Replace(selection.Span, ""); } foreach (var change in textChanges) { edit.Replace(change.Span.ToSpan(), change.NewText); } edit.Apply(); } transaction.Complete(); return; ImmutableArray <TextChange> GetEdits(CancellationToken cancellationToken) { var newLine = textView.Options.GetNewLineCharacter(); var indentationWhitespace = DetermineIndentationWhitespace( parsedDocumentBeforePaste, subjectBuffer, snapshotBeforePaste.AsText(), stringExpressionBeforePaste, cancellationToken); // See if this is a paste of the last copy that we heard about. var edits = TryGetEditsFromKnownCopySource(newLine, indentationWhitespace); if (!edits.IsDefaultOrEmpty) { return(edits); } var pasteWasSuccessful = PasteWasSuccessful( snapshotBeforePaste, snapshotAfterPaste, documentAfterPaste, stringExpressionBeforePaste, cancellationToken); // If not, then just go through the fallback code path that applies more heuristics. var unknownPasteProcessor = new UnknownSourcePasteProcessor( newLine, indentationWhitespace, snapshotBeforePaste, snapshotAfterPaste, documentBeforePaste, documentAfterPaste, stringExpressionBeforePaste, pasteWasSuccessful); return(unknownPasteProcessor.GetEdits()); } ImmutableArray <TextChange> TryGetEditsFromKnownCopySource( string newLine, string indentationWhitespace) { // For simplicity, we only support smart copy/paste when we are pasting into a single contiguous region. if (selectionsBeforePaste.Count != 1) { return(default);