Repository: microsoft/terminal (language: cpp)

Issue:
bracketed paste in conhost
Does the Windows console support [bracket paste mode](https://wpdev.uservoice.com/forums/266908)? I wasn't able to find any information about that.


Discussion / hints:
Funny you should ask! I've been working on this one myself, as I'd love to see it land.
I'll mark this one with our backlog/tracking tags.
one suggestion to keep in mind (as I've seen more than one implementation forget this edge case): make sure to filter out control sequences when pasting. otherwise the buffer could embed the end sequence thus defeating the point of it.

if you want to be compatible with xterm (who invented this feature), you want to filter:
\x00-\x07
\x0b-\x0c
\x0e-\x1f
@DHowett-MSFT  -- worth ingesting this page here concerning the security issues various implementations of bracketed paste mode may have: https://lwn.net/Articles/749992/  -- particularly http://thejh.net/misc/website-terminal-copy-paste
@oising, thank you for that read. I think the best thing we could do is write tests for the specific security issues listed there and attempt to mitigate them as we implement the bracketed paste mode. 
Would it be possible and/or reasonable for starters to just have ConPTY pass through `ESC [ ? 2004 h` and `ESC [ ? 2004 l` on stdout, and `ESC [ 200 ~` and `ESC [ 201 ~` on stdin, so that other terminals that support bracketed paste (e.g. Alacritty) can support it? (It makes me sad when such terminal emulators are blocked from supporting functionality on Windows because ConPTY filters out the magic.)
@DHowett Dustin, thanks for pointing me to this issue from the one I had opened concerning pasting into IPython / vi. Are there any workarounds in the meantime?
In the meantime, before/after pasting in vim you can `:set paste` and `:set nopaste`. I'm not certain what ipy needs here, unfortunately. ☹️
Several similar workarounds for `ipython` on Ubuntu for anyone looking:

* Type `%autoindent` to turn off automatic indentation, then pasting will work.
* Type `%cpaste` and then paste, and then `--` to end, and it will work, if you want to leave automatic indentation on.

This will not persist between `ipython` sessions. To make this change permanent, you can create a configuration file a la `vi ~/.ipython/profile_default/ipython_config.py`, and put these contents in it:

```
c = get_config()
c.InteractiveShell.autoindent = False
```

I hope this helps someone!
For the 8 people who subscribed this (one of them being an obsolete account of Dustin), the bracketed paste mode in Windows Terminal is implemented in #9034 and should be available in 1.7 preview 🎉 . 

Thanks everyone for the discussion about the potential security issues. I believe most of them has been taken care of in the implementation. Feel free to contact me if you have any questions about the current implementation.
@skyline75489  ! Woo
This is fantastic, @skyline75489 - thank you to everyone who worked on it.
Wasn't subscribing, but watching silently for some months - just saw that @cinnamon-msft mentioned this great feature in Terminal Preview 1.7. Looking forward to next month's release!
@gbtec-markusmeier Preview is fairly stable itself; you could switch over today!
Fantastic, thanks for implementing this! I'm closing this issue, because I guess it is all done :)
@davidanthoff my work is to bring bracketed paste into Windows Terminal. The support for bracketed paste in Conhost is yet to be done. I understand this issue is probably opened when Windows Terminal wasn’t really a thing, but here it is. 

We may want to reopen this and change the title to just “bracketed paste in conhost”. 
Done!
Just a big THANK YOU to everyone involved - this is a huge time saver and help for my workflow.



> 
> 
> @davidanthoff my work is to bring bracketed paste into Windows Terminal. The support for bracketed paste in Conhost is yet to be done. I understand this issue is probably opened when Windows Terminal wasn’t really a thing, but here it is.
> 
> We may want to reopen this and change the title to just “bracketed paste in conhost”.

So bracketed paste will not work in ConPTY atm? correct?
No it should work just fine in conpty (the Terminal uses conpty too 😉). Conhost (the vintage console) still needs to implement support for this itself, but that should be much simpler now. 
> 
> 
> No it should work just fine in conpty (the Terminal uses conpty too 😉). Conhost (the vintage console) still needs to implement support for this itself, but that should be much simpler now.

Ok great, is there a preview version on ConPty. or do we have to wait for the new release:
Context using conpty in Gvim:
![image](https://user-images.githubusercontent.com/12205650/110487098-3e749700-80bb-11eb-9c41-bdccccb39fac.png)


I have 1.15.2875.0 but this seems to be unsupported still. 
> I have 1.15.2875.0 but this seems to be unsupported still. 

@dashesy i would encourage you to file a new issue and fill out the issue template. As far as we are aware, paste bracketing works fine for every client application that requests it. Filing a new issue would be the best way to draw attention to, and provide specific information about, the problem you're facing. 

Fix patch:
diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp
index ebc3623dc03..d03d927c19b 100644
--- a/src/cascadia/TerminalCore/Terminal.hpp
+++ b/src/cascadia/TerminalCore/Terminal.hpp
@@ -121,7 +121,7 @@ class Microsoft::Terminal::Core::Terminal final :
     void SetConsoleOutputCP(const unsigned int codepage) noexcept override;
     unsigned int GetConsoleOutputCP() const noexcept override;
     void SetBracketedPasteMode(const bool enabled) noexcept override;
-    std::optional<bool> GetBracketedPasteMode() const noexcept override;
+    bool GetBracketedPasteMode() const noexcept override;
     void CopyToClipboard(std::wstring_view content) override;
     void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override;
     void SetWorkingDirectory(std::wstring_view uri) override;
diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp
index f861b16edc3..3c211d69631 100644
--- a/src/cascadia/TerminalCore/TerminalApi.cpp
+++ b/src/cascadia/TerminalCore/TerminalApi.cpp
@@ -119,7 +119,7 @@ void Terminal::SetBracketedPasteMode(const bool enabled) noexcept
     _bracketedPasteMode = enabled;
 }
 
-std::optional<bool> Terminal::GetBracketedPasteMode() const noexcept
+bool Terminal::GetBracketedPasteMode() const noexcept
 {
     return _bracketedPasteMode;
 }
diff --git a/src/host/consoleInformation.cpp b/src/host/consoleInformation.cpp
index f728945e8ff..0b5b6db272e 100644
--- a/src/host/consoleInformation.cpp
+++ b/src/host/consoleInformation.cpp
@@ -18,37 +18,6 @@
 using Microsoft::Console::Interactivity::ServiceLocator;
 using Microsoft::Console::VirtualTerminal::VtIo;
 
-CONSOLE_INFORMATION::CONSOLE_INFORMATION() :
-    // ProcessHandleList initializes itself
-    pInputBuffer(nullptr),
-    pCurrentScreenBuffer(nullptr),
-    ScreenBuffers(nullptr),
-    OutputQueue(),
-    // ExeAliasList initialized below
-    _OriginalTitle(),
-    _Title(),
-    _Prefix(),
-    _TitleAndPrefix(),
-    _LinkTitle(),
-    Flags(0),
-    PopupCount(0),
-    CP(0),
-    OutputCP(0),
-    CtrlFlags(0),
-    LimitingProcessId(0),
-    // ColorTable initialized below
-    // CPInfo initialized below
-    // OutputCPInfo initialized below
-    _cookedReadData(nullptr),
-    ConsoleIme{},
-    _vtIo(),
-    _blinker{},
-    renderData{}
-{
-    ZeroMemory((void*)&CPInfo, sizeof(CPInfo));
-    ZeroMemory((void*)&OutputCPInfo, sizeof(OutputCPInfo));
-}
-
 bool CONSOLE_INFORMATION::IsConsoleLocked() const noexcept
 {
     return _lock.is_locked();
@@ -176,6 +145,16 @@ void CONSOLE_INFORMATION::SetCookedReadData(COOKED_READ_DATA* readData) noexcept
     _cookedReadData = readData;
 }
 
+bool CONSOLE_INFORMATION::GetBracketedPasteMode() const noexcept
+{
+    return _bracketedPasteMode;
+}
+
+void CONSOLE_INFORMATION::SetBracketedPasteMode(const bool enabled) noexcept
+{
+    _bracketedPasteMode = enabled;
+}
+
 // Method Description:
 // - Return the active screen buffer of the console.
 // Arguments:
diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp
index 82be404df62..2bf21f7dbfa 100644
--- a/src/host/outputStream.cpp
+++ b/src/host/outputStream.cpp
@@ -254,9 +254,7 @@ unsigned int ConhostInternalGetSet::GetConsoleOutputCP() const
 // - <none>
 void ConhostInternalGetSet::SetBracketedPasteMode(const bool enabled)
 {
-    // TODO GH#395: Bracketed Paste Mode is not yet supported in conhost, but we
-    // still keep track of the state so it can be reported by DECRQM.
-    _bracketedPasteMode = enabled;
+    ServiceLocator::LocateGlobals().getConsoleInformation().SetBracketedPasteMode(enabled);
 }
 
 // Routine Description:
@@ -264,12 +262,10 @@ void ConhostInternalGetSet::SetBracketedPasteMode(const bool enabled)
 // Arguments:
 // - <none>
 // Return Value:
-// - true if the mode is enabled, false if not, nullopt if unsupported.
-std::optional<bool> ConhostInternalGetSet::GetBracketedPasteMode() const
+// - true if the mode is enabled, false if not.
+bool ConhostInternalGetSet::GetBracketedPasteMode() const
 {
-    // TODO GH#395: Bracketed Paste Mode is not yet supported in conhost, so we
-    // only report the state if we're tracking it for conpty.
-    return IsConsolePty() ? std::optional{ _bracketedPasteMode } : std::nullopt;
+    return ServiceLocator::LocateGlobals().getConsoleInformation().GetBracketedPasteMode();
 }
 
 // Routine Description:
diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp
index 030dae3f185..f555efbcb1a 100644
--- a/src/host/outputStream.hpp
+++ b/src/host/outputStream.hpp
@@ -61,7 +61,7 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal::
     unsigned int GetConsoleOutputCP() const override;
 
     void SetBracketedPasteMode(const bool enabled) override;
-    std::optional<bool> GetBracketedPasteMode() const override;
+    bool GetBracketedPasteMode() const override;
     void CopyToClipboard(const std::wstring_view content) override;
     void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override;
     void SetWorkingDirectory(const std::wstring_view uri) override;
@@ -80,5 +80,4 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal::
 
 private:
     Microsoft::Console::IIoProvider& _io;
-    bool _bracketedPasteMode{ false };
 };
diff --git a/src/host/server.h b/src/host/server.h
index 72bcfce4ef0..254104e71a7 100644
--- a/src/host/server.h
+++ b/src/host/server.h
@@ -79,29 +79,29 @@ class CONSOLE_INFORMATION :
     public Microsoft::Console::IIoProvider
 {
 public:
-    CONSOLE_INFORMATION();
+    CONSOLE_INFORMATION() = default;
     CONSOLE_INFORMATION(const CONSOLE_INFORMATION& c) = delete;
     CONSOLE_INFORMATION& operator=(const CONSOLE_INFORMATION& c) = delete;
 
     ConsoleProcessList ProcessHandleList;
-    InputBuffer* pInputBuffer;
+    InputBuffer* pInputBuffer = nullptr;
 
-    SCREEN_INFORMATION* ScreenBuffers; // singly linked list
+    SCREEN_INFORMATION* ScreenBuffers = nullptr; // singly linked list
     ConsoleWaitQueue OutputQueue;
 
-    DWORD Flags;
+    DWORD Flags = 0;
 
-    std::atomic<WORD> PopupCount;
+    std::atomic<WORD> PopupCount = 0;
 
     // the following fields are used for ansi-unicode translation
-    UINT CP;
-    UINT OutputCP;
+    UINT CP = 0;
+    UINT OutputCP = 0;
 
-    ULONG CtrlFlags; // indicates outstanding ctrl requests
-    ULONG LimitingProcessId;
+    ULONG CtrlFlags = 0; // indicates outstanding ctrl requests
+    ULONG LimitingProcessId = 0;
 
-    CPINFO CPInfo;
-    CPINFO OutputCPInfo;
+    CPINFO CPInfo = {};
+    CPINFO OutputCPInfo = {};
 
     ConsoleImeInfo ConsoleIme;
 
@@ -125,6 +125,9 @@ class CONSOLE_INFORMATION :
     COOKED_READ_DATA& CookedReadData() noexcept;
     void SetCookedReadData(COOKED_READ_DATA* readData) noexcept;
 
+    bool GetBracketedPasteMode() const noexcept;
+    void SetBracketedPasteMode(const bool enabled) noexcept;
+
     void SetTitle(const std::wstring_view newTitle);
     void SetTitlePrefix(const std::wstring_view newTitlePrefix);
     void SetOriginalTitle(const std::wstring_view originalTitle);
@@ -155,8 +158,9 @@ class CONSOLE_INFORMATION :
     std::wstring _TitleAndPrefix;
     std::wstring _OriginalTitle;
     std::wstring _LinkTitle; // Path to .lnk file
-    SCREEN_INFORMATION* pCurrentScreenBuffer;
-    COOKED_READ_DATA* _cookedReadData; // non-ownership pointer
+    SCREEN_INFORMATION* pCurrentScreenBuffer = nullptr;
+    COOKED_READ_DATA* _cookedReadData = nullptr; // non-ownership pointer
+    bool _bracketedPasteMode = false;
 
     Microsoft::Console::VirtualTerminal::VtIo _vtIo;
     Microsoft::Console::CursorBlinker _blinker;
diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp
index ec1a136a79f..55909fdb962 100644
--- a/src/interactivity/win32/Clipboard.cpp
+++ b/src/interactivity/win32/Clipboard.cpp
@@ -108,7 +108,9 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData,
 
     try
     {
-        auto inEvents = TextToKeyEvents(pData, cchData);
+        const auto vtInputMode = gci.pInputBuffer->IsInVirtualTerminalInputMode();
+        const auto bracketedPasteMode = gci.GetBracketedPasteMode();
+        auto inEvents = TextToKeyEvents(pData, cchData, vtInputMode && bracketedPasteMode);
         gci.pInputBuffer->Write(inEvents);
     }
     catch (...)
@@ -127,16 +129,31 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData,
 // Arguments:
 // - pData - the text to convert
 // - cchData - the size of pData, in wchars
+// - bracketedPaste - should this be bracketed with paste control sequences
 // Return Value:
 // - deque of KeyEvents that represent the string passed in
 // Note:
 // - will throw exception on error
 std::deque<std::unique_ptr<IInputEvent>> Clipboard::TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
-                                                                    const size_t cchData)
+                                                                    const size_t cchData,
+                                                                    const bool bracketedPaste)
 {
     THROW_HR_IF_NULL(E_INVALIDARG, pData);
 
     std::deque<std::unique_ptr<IInputEvent>> keyEvents;
+    const auto pushControlSequence = [&](const std::wstring_view sequence) {
+        std::for_each(sequence.begin(), sequence.end(), [&](const auto wch) {
+            keyEvents.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, wch, 0));
+            keyEvents.push_back(std::make_unique<KeyEvent>(false, 1ui16, 0ui16, 0ui16, wch, 0));
+        });
+    };
+
+    // When a bracketed paste is requested, we need to wrap the text with
+    // control sequences which indicate that the content has been pasted.
+    if (bracketedPaste)
+    {
+        pushControlSequence(L"\x1b[200~");
+    }
 
     for (size_t i = 0; i < cchData; ++i)
     {
@@ -148,8 +165,10 @@ std::deque<std::unique_ptr<IInputEvent>> Clipboard::TextToKeyEvents(_In_reads_(c
         const auto skipLinefeed = (i != 0 &&
                                    currentChar == UNICODE_LINEFEED &&
                                    pData[i - 1] == UNICODE_CARRIAGERETURN);
+        // filter out escape if bracketed paste mode is enabled
+        const auto skipEscape = (bracketedPaste && currentChar == UNICODE_ESC);
 
-        if (!charAllowed || skipLinefeed)
+        if (!charAllowed || skipLinefeed || skipEscape)
         {
             continue;
         }
@@ -182,6 +201,11 @@ std::deque<std::unique_ptr<IInputEvent>> Clipboard::TextToKeyEvents(_In_reads_(c
             convertedEvents.pop_front();
         }
     }
+
+    if (bracketedPaste)
+    {
+        pushControlSequence(L"\x1b[201~");
+    }
     return keyEvents;
 }
 
diff --git a/src/interactivity/win32/clipboard.hpp b/src/interactivity/win32/clipboard.hpp
index da28f2dfc0e..31413eb61ed 100644
--- a/src/interactivity/win32/clipboard.hpp
+++ b/src/interactivity/win32/clipboard.hpp
@@ -36,7 +36,8 @@ namespace Microsoft::Console::Interactivity::Win32
 
     private:
         std::deque<std::unique_ptr<IInputEvent>> TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
-                                                                 const size_t cchData);
+                                                                 const size_t cchData,
+                                                                 const bool bracketedPaste = false);
 
         void StoreSelectionToClipboard(_In_ const bool fAlsoCopyFormatting);
 
diff --git a/src/terminal/adapter/ITerminalApi.hpp b/src/terminal/adapter/ITerminalApi.hpp
index bcf635907a9..b0987a22014 100644
--- a/src/terminal/adapter/ITerminalApi.hpp
+++ b/src/terminal/adapter/ITerminalApi.hpp
@@ -65,7 +65,7 @@ namespace Microsoft::Console::VirtualTerminal
         virtual unsigned int GetConsoleOutputCP() const = 0;
 
         virtual void SetBracketedPasteMode(const bool enabled) = 0;
-        virtual std::optional<bool> GetBracketedPasteMode() const = 0;
+        virtual bool GetBracketedPasteMode() const = 0;
         virtual void CopyToClipboard(const std::wstring_view content) = 0;
         virtual void SetTaskbarProgress(const DispatchTypes::TaskbarState state, const size_t progress) = 0;
         virtual void SetWorkingDirectory(const std::wstring_view uri) = 0;
diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp
index 862d449031b..2c260c859ba 100644
--- a/src/terminal/adapter/ut_adapter/adapterTest.cpp
+++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp
@@ -189,10 +189,10 @@ class TestGetSet final : public ITerminalApi
         Log::Comment(L"SetBracketedPasteMode MOCK called...");
     }
 
-    std::optional<bool> GetBracketedPasteMode() const override
+    bool GetBracketedPasteMode() const override
     {
         Log::Comment(L"GetBracketedPasteMode MOCK called...");
-        return {};
+        return false;
     }
 
     void CopyToClipboard(const std::wstring_view /*content*/)


Test patch: