Repository: microsoft/terminal (language: cpp)

Issue:
Pasting multiple lines doesn't execute the last line 
### Windows Terminal version

1.13.10336.0

### Windows build number

10.0.19042.0

### Other Software

_No response_

### Steps to reproduce

Copy multiple powershell commands from any text editor.
Paste them into the powershell core tab of Windows Terminal.
All lines execute except the last line. (even though i've included one or 2 blanks lines after the last line of code - to ensure getting a new line or enter)


### Expected Behavior

All pasted lines of code should execute as expected.

### Actual Behavior

You must manually press the enter key to get the last command to execute.


Discussion / hints:
Ugh!  I'm so sorry.  I see there's a setting to remove trailing white-space when pasting.  It's either new or got set to on with the recent upgrade.  I flipped it to off and things are back to working as expected.    I apologize.
ACTUALLY you know what, I'm reopening this. If there's a multiline paste that has trailing whitespace, that we should keep. We should only trim the trailing whitespace if there's a single line paste. I hit this all weekend and it annoyed the bajesus out of me. #11473



Fix patch:
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index 32d4fb67299..86da0efd240 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -2163,18 +2163,12 @@ namespace winrt::TerminalApp::implementation
 
             if (_settings.GlobalSettings().TrimPaste())
             {
-                std::wstring_view textView{ text };
-                const auto pos = textView.find_last_not_of(L"\t\n\v\f\r ");
-                if (pos == textView.npos)
+                text = { Utils::TrimPaste(text) };
+                if (text.empty())
                 {
                     // Text is all white space, nothing to paste
                     co_return;
                 }
-                else if (const auto toRemove = textView.size() - 1 - pos; toRemove > 0)
-                {
-                    textView.remove_suffix(toRemove);
-                    text = { textView };
-                }
             }
 
             // If the requesting terminal is in bracketed paste mode, then we don't need to warn about a multi-line paste.
diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp
index cef2620e806..0efae695985 100644
--- a/src/types/inc/utils.hpp
+++ b/src/types/inc/utils.hpp
@@ -106,4 +106,9 @@ namespace Microsoft::Console::Utils
     std::tuple<std::wstring, std::wstring> MangleStartingDirectoryForWSL(std::wstring_view commandLine,
                                                                          std::wstring_view startingDirectory);
 
+    // Similar to MangleStartingDirectoryForWSL, this function is only ever used
+    // in TerminalPage::_PasteFromClipboardHandler, but putting it here makes
+    // testing easier.
+    std::wstring_view TrimPaste(std::wstring_view textView) noexcept;
+
 }
diff --git a/src/types/ut_types/UtilsTests.cpp b/src/types/ut_types/UtilsTests.cpp
index b024f9a175d..707a0f25501 100644
--- a/src/types/ut_types/UtilsTests.cpp
+++ b/src/types/ut_types/UtilsTests.cpp
@@ -30,6 +30,9 @@ class UtilsTests
     TEST_METHOD(TestMangleWSLPaths);
 #endif
 
+    TEST_METHOD(TestTrimTrailingWhitespace);
+    TEST_METHOD(TestDontTrimTrailingWhitespace);
+
     void _VerifyXTermColorResult(const std::wstring_view wstr, DWORD colorValue);
     void _VerifyXTermColorInvalid(const std::wstring_view wstr);
 };
@@ -505,3 +508,41 @@ void UtilsTests::TestMangleWSLPaths()
     }
 }
 #endif
+
+void UtilsTests::TestTrimTrailingWhitespace()
+{
+    // Continue on failures
+    const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
+
+    // Tests for GH #11473
+    VERIFY_ARE_EQUAL(L"Foo", TrimPaste(L"Foo   "));
+    VERIFY_ARE_EQUAL(L"Foo", TrimPaste(L"Foo\n"));
+    VERIFY_ARE_EQUAL(L"Foo", TrimPaste(L"Foo\n\n"));
+    VERIFY_ARE_EQUAL(L"Foo", TrimPaste(L"Foo\r\n"));
+    VERIFY_ARE_EQUAL(L"Foo Bar", TrimPaste(L"Foo Bar\n"));
+    VERIFY_ARE_EQUAL(L"Foo\tBar", TrimPaste(L"Foo\tBar\n"));
+
+    VERIFY_ARE_EQUAL(L"Foo Bar", TrimPaste(L"Foo Bar\t"), L"Trim when there is a tab at the end.");
+    VERIFY_ARE_EQUAL(L"Foo Bar", TrimPaste(L"Foo Bar\t\t"), L"Trim when there are tabs at the end.");
+    VERIFY_ARE_EQUAL(L"Foo Bar", TrimPaste(L"Foo Bar\t\n"), L"Trim when there are tabs at the start of the whitespace at the end.");
+    VERIFY_ARE_EQUAL(L"Foo\tBar", TrimPaste(L"Foo\tBar\t\n"), L"Trim when there are tabs in the middle of the string, and in the whitespace at the end.");
+    VERIFY_ARE_EQUAL(L"Foo\tBar", TrimPaste(L"Foo\tBar\n\t"), L"Trim when there are tabs in the middle of the string, and in the whitespace at the end.");
+    VERIFY_ARE_EQUAL(L"Foo\tBar", TrimPaste(L"Foo\tBar\t\n\t"), L"Trim when there are tabs in the middle of the string, and in the whitespace at the end.");
+}
+void UtilsTests::TestDontTrimTrailingWhitespace()
+{
+    // Continue on failures
+    const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
+
+    VERIFY_ARE_EQUAL(L"Foo\tBar", TrimPaste(L"Foo\tBar"));
+
+    // Tests for GH #12387
+    VERIFY_ARE_EQUAL(L"Foo\nBar\n", TrimPaste(L"Foo\nBar\n"));
+    VERIFY_ARE_EQUAL(L"Foo  Baz\nBar\n", TrimPaste(L"Foo  Baz\nBar\n"));
+    VERIFY_ARE_EQUAL(L"Foo\tBaz\nBar\n", TrimPaste(L"Foo\tBaz\nBar\n"), L"Don't trim when there's a trailing newline, and tabs in the middle");
+    VERIFY_ARE_EQUAL(L"Foo\tBaz\nBar\t\n", TrimPaste(L"Foo\tBaz\nBar\t\n"), L"Don't trim when there's a trailing newline, and tabs in the middle");
+
+    // We need to both
+    // * trim when there's a tab followed by only whitespace
+    // * not trim then there's a tab in the middle, and the string ends in whitespace
+}
diff --git a/src/types/utils.cpp b/src/types/utils.cpp
index 10e7661ce77..8d4edf1e75b 100644
--- a/src/types/utils.cpp
+++ b/src/types/utils.cpp
@@ -772,3 +772,28 @@ std::tuple<std::wstring, std::wstring> Utils::MangleStartingDirectoryForWSL(std:
                                     std::wstring{ startingDirectory }
     };
 }
+
+std::wstring_view Utils::TrimPaste(std::wstring_view textView) noexcept
+{
+    const auto lastNonSpace = textView.find_last_not_of(L"\t\n\v\f\r ");
+    const auto firstNewline = textView.find_first_of(L"\n\v\f\r");
+
+    const bool isOnlyWhitespace = lastNonSpace == textView.npos;
+    const bool isMultiline = firstNewline < lastNonSpace;
+
+    if (isOnlyWhitespace)
+    {
+        // Text is all white space, nothing to paste
+        return L"";
+    }
+
+    if (isMultiline)
+    {
+        // In this case, the user totally wanted to paste multiple lines of text,
+        // and that likely includes the trailing newline.
+        // DON'T trim it in this case.
+        return textView;
+    }
+
+    return textView.substr(0, lastNonSpace + 1);
+}


Test patch: