Beispiel #1
0
 public void ExecuteCommand(PasteCommandArgs args, Action nextHandler, CommandExecutionContext context)
 {
     HandlePossibleTypingCommand(args, nextHandler, span =>
     {
         nextHandler();
     });
 }
Beispiel #2
0
        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);
     }
 }
Beispiel #4
0
 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));
 }
Beispiel #5
0
        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);
        }
Beispiel #6
0
        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);
        }
Beispiel #7
0
        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());
 }
Beispiel #12
0
 CommandState ICommandHandler <PasteCommandArgs> .GetCommandState(PasteCommandArgs args)
 {
     return(CommandState.Available);
 }
Beispiel #13
0
 void ICommandHandler <PasteCommandArgs> .ExecuteCommand(PasteCommandArgs args, Action nextHandler)
 {
     AssertIsForeground();
     EnsureCompletionSessionStopped();
     nextHandler();
 }
Beispiel #14
0
 bool ICommandHandler <PasteCommandArgs> .ExecuteCommand(PasteCommandArgs args, CommandExecutionContext executionContext)
 {
     GetOperations(args.TextView).Paste();
     return(true);
 }
 public CommandState GetCommandState(PasteCommandArgs args, Func <CommandState> nextHandler)
 => nextHandler();
Beispiel #16
0
 void ICommandHandler <PasteCommandArgs> .ExecuteCommand(PasteCommandArgs args, Action nextHandler)
 {
     AssertIsForeground();
     DismissSessionIfActive();
     nextHandler();
 }
Beispiel #17
0
 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));
 }
Beispiel #20
0
 void IChainedCommandHandler <PasteCommandArgs> .ExecuteCommand(PasteCommandArgs args, Action nextHandler, CommandExecutionContext context)
 {
     AssertIsForeground();
     DismissSessionIfActive();
     nextHandler();
 }
Beispiel #21
0
        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);