Skip to content

Commit

Permalink
Add support for bracketed paste mode in ConHost (#15155)
Browse files Browse the repository at this point in the history
This adds support for XTerm's "bracketed paste" mode in ConHost. When
enabled, any pasted text is bracketed with a pair of escape sequences,
which lets the receiving application know that the content was pasted
rather than typed.

## References and Relevant Issues

Bracketed paste mode was added to Windows Terminal in PR #9034.
Adding it to ConHost ticks one more item off the list in #13408. 

## Detailed Description of the Pull Request / Additional comments

This only applies when VT input mode is enabled, since that is the way
Windows Terminal currently works.

When it comes to filtering, though, the only change I've made is to
filter out the escape character, and only when bracketed mode is
enabled. That's necessary to prevent any attempts to bypass the
bracketing, but I didn't want to mess with the expected behavior for
legacy apps if bracketed mode is disabled.

## Validation Steps Performed

Manually tested in bash with `bind 'set enable-bracketed-paste on'` and
confirmed that pasted content is now buffered, instead of being executed
immediately.

Also tested in VIM, and confirmed that you can now paste preformatted
code without the autoindent breaking the formatting.

Closes #395
  • Loading branch information
j4james committed Apr 26, 2023
1 parent 2e3d5e6 commit 6030616
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 63 deletions.
2 changes: 1 addition & 1 deletion src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalCore/TerminalApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
41 changes: 10 additions & 31 deletions src/host/consoleInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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:
Expand Down
12 changes: 4 additions & 8 deletions src/host/outputStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,22 +254,18 @@ 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:
// - Gets the current state of XTerm bracketed paste mode.
// 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:
Expand Down
3 changes: 1 addition & 2 deletions src/host/outputStream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -80,5 +80,4 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal::

private:
Microsoft::Console::IIoProvider& _io;
bool _bracketedPasteMode{ false };
};
30 changes: 17 additions & 13 deletions src/host/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
30 changes: 27 additions & 3 deletions src/interactivity/win32/Clipboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 (...)
Expand All @@ -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)
{
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand Down
3 changes: 2 additions & 1 deletion src/interactivity/win32/clipboard.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion src/terminal/adapter/ITerminalApi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/terminal/adapter/ut_adapter/adapterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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*/)
Expand Down

0 comments on commit 6030616

Please sign in to comment.