Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow copying with ANSI escape code control sequences #17059

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/cascadia/profiles.schema.json
Expand Up @@ -851,6 +851,11 @@
"default": false,
"description": "If true, the copied content will be copied as a single line (even if there are hard line breaks present in the text). If false, newlines persist from the selected text."
},
"withControlSequences": {
"type": "boolean",
"default": false,
"description": "If true, copied content will contain ANSI escape code control sequences representing the styling of the content."
},
"dismissSelection": {
"type": "boolean",
"default": true,
Expand Down
98 changes: 81 additions & 17 deletions src/buffer/out/textBuffer.cpp
Expand Up @@ -2102,6 +2102,32 @@ std::wstring TextBuffer::GetPlainText(const CopyRequest& req) const
return selectedText;
}

// Routine Description:
// - Retrieves the text data from the buffer *with* ANSI escape code control sequences and presents it in a clipboard-ready format.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, any guidance on comment max length? I didn't wrap any of the doc comments I added, they get a bit long heh 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I try to stick to around 120 characters and 140 at most, because then you can still comfortably read it on smaller ~14" laptop screens. In this instance I'd probably split it into 2 lines and make them even length.

FYI you don't need to write "Routine Description:" on new functions. This is only a hold over from our old code base.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, I'll go ahead and reflow the comments I've added (and remove that header haha) 👍

// Arguments:
// - req - the copy request having the bounds of the selected region and other related configuration flags.
// Return Value:
// - The text and control sequence data from the selected region of the text buffer. Empty if the copy request is invalid.
std::wstring TextBuffer::GetWithControlSequences(const CopyRequest& req) const
{
if (req.beg > req.end)
{
return {};
}

std::wstring selectedText;

ChunkedSerialize(0, L"", &req, [&](std::wstring& buffer, bool isDone) {
if (isDone)
{
// We don't actually care about the old selectedText / new buffer value, just want an efficient way to get the buffer contents.
selectedText.swap(buffer);
}
});

return selectedText;
}

// Routine Description:
// - Generates a CF_HTML compliant structure from the selected region of the buffer
// Arguments:
Expand Down Expand Up @@ -2533,17 +2559,36 @@ void TextBuffer::_AppendRTFText(std::string& contentBuilder, const std::wstring_
}
}

void TextBuffer::Serialize(const wchar_t* destination) const
void TextBuffer::SerializeToPath(const wchar_t* destination) const
{
const wil::unique_handle file{ CreateFileW(destination, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
THROW_LAST_ERROR_IF(!file);

static constexpr size_t writeThreshold = 32 * 1024;
ChunkedSerialize(writeThreshold, L"\uFEFF", nullptr, [&](std::wstring& buffer, bool /* isDone */) {
const auto fileSize = gsl::narrow<DWORD>(buffer.size() * sizeof(wchar_t));
DWORD bytesWritten = 0;
THROW_IF_WIN32_BOOL_FALSE(WriteFile(file.get(), buffer.data(), fileSize, &bytesWritten, nullptr));
THROW_WIN32_IF_MSG(ERROR_WRITE_FAULT, bytesWritten != fileSize, "failed to write");
buffer.clear();
});
Copy link
Contributor

@tusharsnx tusharsnx Apr 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible refactoring:
I think we can avoid std::function usage here, by making ChunkedSerialize chunk buffer in row-by-row fashion instead of bytes-sized data. Then, both SerializeToPath and GetWithControlSequences can decide to read more or flush based on their specific logic.

This might be more complicated, so it's okay to not get into this right now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if you decide to do this, then rename ChunkedSerialize to SerializeRow to be explicit.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is a fantastic idea, it really simplifies things! ChunkedSerialize felt like a clunky interface with the callback and it knew too much about how it might be used (changing behavior based on whether or not copyReq was passed). This refactor is much cleaner, I think, and completely invalidates a number of the questions I had below (no longer have the inline lambda for the rowInfo, no longer have the question about how to pass a maybe-CopyReq) 🙏

}

// Routine Description:
// - Serializes the text buffer in chunks including ANSI escape code control sequences, and passes those chunks to the callback once they're large enough.
// Arguments:
// - writeThreshold - How big the chunk needs to be before passing it to the `chunkReadyCallback`. If 0, won't pass to the `chunkReadyCallback` until fully done serializing (effectively turns off the chunking mechanism).
// - prefix - A string to put at the beginning of the very first chunk.
// - copyReq - If present, controls the bounds of the selected region and other related configuration flags. If not present, the whole TextBuffer is serialized.
// - chunkReadyCallback - Called with the chunk when the chunk is bigger than the `writeThreshold` (if non-zero) and at the very end of the serialization process (in which case `isDone` will be `true`).
void TextBuffer::ChunkedSerialize(const size_t writeThreshold, const std::wstring prefix, const CopyRequest* copyReq, std::function<void(std::wstring& chunk, const bool isDone)> chunkReadyCallback) const
lhecker marked this conversation as resolved.
Show resolved Hide resolved
{
std::wstring buffer;
buffer.reserve(writeThreshold + writeThreshold / 2);
buffer.push_back(L'\uFEFF');
buffer += prefix;

const til::CoordType lastRowWithText = GetLastNonSpaceCharacter(nullptr).y;
const til::CoordType firstRow = copyReq != nullptr ? copyReq->beg.y : 0;
const til::CoordType lastRow = copyReq != nullptr ? copyReq->end.y : GetLastNonSpaceCharacter(nullptr).y;
CharacterAttributes previousAttr = CharacterAttributes::Unused1;
TextColor previousFg;
TextColor previousBg;
Expand All @@ -2552,7 +2597,7 @@ void TextBuffer::Serialize(const wchar_t* destination) const

// This iterates through each row. The exit condition is at the end
// of the for() loop so that we can properly handle file flushing.
for (til::CoordType currentRow = 0;; currentRow++)
for (til::CoordType currentRow = firstRow;; currentRow++)
{
const auto& row = GetRowByOffset(currentRow);

Expand All @@ -2567,11 +2612,36 @@ void TextBuffer::Serialize(const wchar_t* destination) const
buffer.append(til::at(mappings, idx));
}

const auto& runs = row.Attributes().runs();
const auto moreRowsRemaining = currentRow < lastRow;

// Need to store this off into a local to avoid dangling references.
const auto rowInfo = [&] {
lhecker marked this conversation as resolved.
Show resolved Hide resolved
if (copyReq != nullptr)
{
const auto [rowBeg, rowEnd, copyAddLineBreak] = _RowCopyHelper(*copyReq, currentRow, row);
const auto rowBegU16 = gsl::narrow_cast<uint16_t>(rowBeg);
const auto rowEndU16 = gsl::narrow_cast<uint16_t>(rowEnd);

const auto runs = row.Attributes().slice(rowBegU16, rowEndU16).runs();
const int startX = rowBeg;
const bool addLineBreak = copyAddLineBreak && currentRow != lastRow;
return std::tuple{ runs, startX, addLineBreak };
}
else
{
const auto runs = row.Attributes().runs();
const int startX = 0;
const bool addLineBreak = !row.WasWrapForced() || !moreRowsRemaining;
return std::tuple{ runs, startX, addLineBreak };
}
}();
const auto [runs, startX, addLineBreak] = rowInfo;

auto it = runs.begin();
const auto end = runs.end();
const auto last = end - 1;
til::CoordType oldX = 0;
// Don't try to get end-1 if it's an empty iterator; in this case we're going to ignore the `last` value anyway so just use `end`.
const auto last = it == end ? end : end - 1;
til::CoordType oldX = startX;

for (; it != end; ++it)
{
Expand Down Expand Up @@ -2756,27 +2826,21 @@ void TextBuffer::Serialize(const wchar_t* destination) const
{
// This can result in oldX > newX, but that's okay because GetText()
// is robust against that and returns an empty string.
newX = row.MeasureRight();
newX = std::min(newX, row.MeasureRight());
}

buffer.append(row.GetText(oldX, newX));
oldX = newX;
}

const auto moreRowsRemaining = currentRow < lastRowWithText;

if (!row.WasWrapForced() || !moreRowsRemaining)
if (addLineBreak)
{
buffer.append(L"\r\n");
}

if (buffer.size() >= writeThreshold || !moreRowsRemaining)
if ((writeThreshold > 0 && buffer.size() >= writeThreshold) || !moreRowsRemaining)
{
const auto fileSize = gsl::narrow<DWORD>(buffer.size() * sizeof(wchar_t));
DWORD bytesWritten = 0;
THROW_IF_WIN32_BOOL_FALSE(WriteFile(file.get(), buffer.data(), fileSize, &bytesWritten, nullptr));
THROW_WIN32_IF_MSG(ERROR_WRITE_FAULT, bytesWritten != fileSize, "failed to write");
buffer.clear();
chunkReadyCallback(buffer, !moreRowsRemaining);
}

if (!moreRowsRemaining)
Expand Down
5 changes: 4 additions & 1 deletion src/buffer/out/textBuffer.hpp
Expand Up @@ -271,6 +271,8 @@ class TextBuffer final

std::wstring GetPlainText(const CopyRequest& req) const;

std::wstring GetWithControlSequences(const CopyRequest& req) const;

std::string GenHTML(const CopyRequest& req,
const int fontHeightPoints,
const std::wstring_view fontFaceName,
Expand All @@ -285,7 +287,8 @@ class TextBuffer final
const bool isIntenseBold,
std::function<std::tuple<COLORREF, COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors) const noexcept;

void Serialize(const wchar_t* destination) const;
void SerializeToPath(const wchar_t* destination) const;
void ChunkedSerialize(const size_t writeThreshold, const std::wstring prefix, const CopyRequest* copyReq, std::function<void(std::wstring& buffer, const bool isDone)> callback) const;

struct PositionInformation
{
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/AppActionHandlers.cpp
Expand Up @@ -548,7 +548,7 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<CopyTextArgs>())
{
const auto handled = _CopyText(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.CopyFormatting());
const auto handled = _CopyText(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.WithControlSequences(), realArgs.CopyFormatting());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we can add a new CopyFormat, CopyFormat::VT, that would avoid the need for plumbing WithControlSequences down to ControlCore + introducing a new setting. It will just be carried forward with other copy formats.

If the user has "vt" in the copyFormatting list:

"copyFormatting": ["vt"],

we copy the text in the new VT format (including in any other formats set by the user).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add the new format VT = 0x04 here:

enum CopyFormat
{
HTML = 0x1,
RTF = 0x2,
All = 0xffffffff
};

In ControlCore::CopySelectionToClipboard, you can check if we need to copy in VT format like so:

const auto copyVt = WI_IsFlagSet(copyFormats, CopyFormat::VT);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I had the same thought when implementing this but wasn't sure if that's actually what we want here. In particular, currently the formats represent a wholly different clipboard format. That being said singleLine does affect all the formats so it's a bit odd to pass this boolean through to just affect the "plain text" (CF_UNICODETEXT) format, so I'm certainly open to having a "pseudo format" here that modifies the plain text rather than opting into another one! Not totally sure how to make this call haha.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surely this isn't plain text, so it should actually be a separate clipboard format? Personally I would expect it to paste as ANSI only if the application can handle that, and the plain text format should still be available for applications that just accept text. Typically it should be the app's decision as to what format they want.

That said, I don't know if there's a generally agreed upon clipboard registration for ANSI text that will work automatically, but worst case I think apps should be able to offer users a choice of the available formats, and let them decide want they want at the time of the paste.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, I don't know if there's a generally agreed upon clipboard registration for ANSI text that will work automatically, but worst case I think apps should be able to offer users a choice of the available formats, and let them decide want they want at the time of the paste.

As far as I'm aware there's no such format. It likely wouldn't work with a custom format either. My understanding is that the goal is that you can paste the ANSI text into markdown documents. For instance Discord supports this:
image

This will only work if the ANSI text is in the CF_UNICODETEXT format.

We've briefly talked about this as a team today and we felt like this new format should not be exposed in the settings UI here:
image

Precisely because of the interop issue that James mentioned. Instead it could be triggered with an action via the command palette. That is, we could for instance introduce a new action type for this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an ideal world, the apps that wanted functionality like this would have got together and decided on a custom clipboard format name, say "ANSIX3.64", which they'd all agree to support. Then when someone like Discord saw that on the clipboard, they could have automatically pasted it surrounded by their ansi markdown wrapper. Having to do that manually is sad. But I guess we have to live with what we've got.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hahaha indeed, I'm the person who hacked in the ansi "syntax highlighting" in Discord 😅

It's interesting (and, to be clear, I'm not sure what the "right" answer is here or if there is one), but in the context of using this in markdown (aka in Discord or in some other markdown as mentioned in #15703), what you're authoring is plaintext. Like, if I copied code from a website and it populated both the unicode and the HTML formats in the clipboard, when I pasted it into my markdown editor I'd personally expect it to paste the plaintext and not try to... paste formatted/wysiwyg contents, or to auto-insert a code block for me. And (again, in my opinion/use cases) similarly here. I would expect this to act less like a seamless parallel copy and more like a different behavior.

introduce a new action type for this

Especially given the above that makes sense! Would that mean something like what's done here, or not piggy backing on the copy action at all and introducing a new one with it's own arguments (if any)?

I really appreciate the discussion here, as well as you taking the time to talk this over with your team! 🙏

Copy link
Collaborator

@j4james j4james Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when I pasted it into my markdown editor I'd personally expect it to paste the plaintext and not try to... paste formatted/wysiwyg contents

This is what the "Paste Special" menu option is for. Have a look at WordPad if you want to see an example of how it's supposed to work. Just type in some text with some formatting and copy it to the clipboard, then press Ctrl+Alt+V. You should see a popup dialog offering you a bunch of choices for how you want the content to paste. If you paste as "Rich Text" it'll keep the formatting. If you paste as "Unformatted Text", you just get the plain text.

If you're responsible for the Discord clipboard code, you should be able to implement the same sort of thing. So by default you can paste as plain text, but still give users the option to paste as ANSI via a "Paste Special" context menu, or shortcut key.

Edit: I should add that I don't feel strongly about this if everyone else disagrees with me, or it's too much effort. It's just that this is the way I would have expected it to work in a perfect world.

args.Handled(handled);
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Expand Up @@ -2856,14 +2856,15 @@ namespace winrt::TerminalApp::implementation
// Arguments:
// - dismissSelection: if not enabled, copying text doesn't dismiss the selection
// - singleLine: if enabled, copy contents as a single line of text
// - withControlSequences: if enabled, the copied plain text contains color/style ANSI escape codes from the selection
// - formats: dictate which formats need to be copied
// Return Value:
// - true iff we we able to copy text (if a selection was active)
bool TerminalPage::_CopyText(const bool dismissSelection, const bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats)
bool TerminalPage::_CopyText(const bool dismissSelection, const bool singleLine, const bool withControlSequences, const Windows::Foundation::IReference<CopyFormat>& formats)
{
if (const auto& control{ _GetActiveControl() })
{
return control.CopySelectionToClipboard(dismissSelection, singleLine, formats);
return control.CopySelectionToClipboard(dismissSelection, singleLine, withControlSequences, formats);
}
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/TerminalPage.h
Expand Up @@ -401,7 +401,7 @@ namespace winrt::TerminalApp::implementation
bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri);

void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri);
bool _CopyText(const bool dismissSelection, const bool singleLine, const Windows::Foundation::IReference<Microsoft::Terminal::Control::CopyFormat>& formats);
bool _CopyText(const bool dismissSelection, const bool singleLine, const bool withControlSequences, const Windows::Foundation::IReference<Microsoft::Terminal::Control::CopyFormat>& formats);

winrt::fire_and_forget _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs);

Expand Down
6 changes: 4 additions & 2 deletions src/cascadia/TerminalControl/ControlCore.cpp
Expand Up @@ -568,7 +568,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
else if (vkey == VK_RETURN && !mods.IsCtrlPressed() && !mods.IsAltPressed())
{
// [Shift +] Enter --> copy text
CopySelectionToClipboard(mods.IsShiftPressed(), nullptr);
CopySelectionToClipboard(mods.IsShiftPressed(), false, nullptr);
_terminal->ClearSelection();
_updateSelectionUI();
return true;
Expand Down Expand Up @@ -1307,9 +1307,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Windows Clipboard (CascadiaWin32:main.cpp).
// Arguments:
// - singleLine: collapse all of the text to one line
// - withControlSequences: if enabled, the copied plain text contains color/style ANSI escape codes from the selection
// - formats: which formats to copy (defined by action's CopyFormatting arg). nullptr
// if we should defer which formats are copied to the global setting
bool ControlCore::CopySelectionToClipboard(bool singleLine,
bool withControlSequences,
const Windows::Foundation::IReference<CopyFormat>& formats)
{
::Microsoft::Terminal::Core::Terminal::TextCopyData payload;
Expand All @@ -1331,7 +1333,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation

// extract text from buffer
// RetrieveSelectedTextFromBuffer will lock while it's reading
payload = _terminal->RetrieveSelectedTextFromBuffer(singleLine, copyHtml, copyRtf);
payload = _terminal->RetrieveSelectedTextFromBuffer(singleLine, withControlSequences, copyHtml, copyRtf);
}

copyToClipboard(payload.plainText, payload.html, payload.rtf);
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalControl/ControlCore.h
Expand Up @@ -121,7 +121,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation

void SendInput(const winrt::hstring& wstr);
void PasteText(const winrt::hstring& hstr);
bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
bool CopySelectionToClipboard(bool singleLine, bool withControlSequences, const Windows::Foundation::IReference<CopyFormat>& formats);
void SelectAll();
void ClearSelection();
bool ToggleBlockSelection();
Expand Down
8 changes: 5 additions & 3 deletions src/cascadia/TerminalControl/ControlInteractivity.cpp
Expand Up @@ -194,9 +194,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Windows Clipboard (CascadiaWin32:main.cpp).
// Arguments:
// - singleLine: collapse all of the text to one line
// - withControlSequences: if enabled, the copied plain text contains color/style ANSI escape codes from the selection
// - formats: which formats to copy (defined by action's CopyFormatting arg). nullptr
// if we should defer which formats are copied to the global setting
bool ControlInteractivity::CopySelectionToClipboard(bool singleLine,
bool withControlSequences,
const Windows::Foundation::IReference<CopyFormat>& formats)
{
if (_core)
Expand All @@ -213,7 +215,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Mark the current selection as copied
_selectionNeedsToBeCopied = false;

return _core->CopySelectionToClipboard(singleLine, formats);
return _core->CopySelectionToClipboard(singleLine, withControlSequences, formats);
}

return false;
Expand Down Expand Up @@ -312,7 +314,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
else
{
// Try to copy the text and clear the selection
const auto successfulCopy = CopySelectionToClipboard(shiftEnabled, nullptr);
const auto successfulCopy = CopySelectionToClipboard(shiftEnabled, false, nullptr);
_core->ClearSelection();
if (_core->CopyOnSelect() || !successfulCopy)
{
Expand Down Expand Up @@ -441,7 +443,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// IMPORTANT!
// DO NOT clear the selection here!
// Otherwise, the selection will be cleared immediately after you make it.
CopySelectionToClipboard(false, nullptr);
CopySelectionToClipboard(false, false, nullptr);
}

_singleClickTouchdownPos = std::nullopt;
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/ControlInteractivity.h
Expand Up @@ -83,6 +83,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
#pragma endregion

bool CopySelectionToClipboard(bool singleLine,
bool withControlSequences,
const Windows::Foundation::IReference<CopyFormat>& formats);
void RequestPasteTextFromClipboard();
void SetEndSelectionPoint(const Core::Point pixelPosition);
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalControl/ControlInteractivity.idl
Expand Up @@ -32,7 +32,7 @@ namespace Microsoft.Terminal.Control

InteractivityAutomationPeer OnCreateAutomationPeer();

Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference<CopyFormat> formats);
Boolean CopySelectionToClipboard(Boolean singleLine, Boolean withControlSequences, Windows.Foundation.IReference<CopyFormat> formats);
void RequestPasteTextFromClipboard();
void SetEndSelectionPoint(Microsoft.Terminal.Core.Point point);

Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalControl/HwndTerminal.cpp
Expand Up @@ -116,7 +116,7 @@ try
const auto lock = publicTerminal->_terminal->LockForWriting();
if (publicTerminal->_terminal->IsSelectionActive())
{
const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false, true, true);
const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false, false, true, true);
LOG_IF_FAILED(publicTerminal->_CopyTextToSystemClipboard(bufferData.plainText, bufferData.html, bufferData.rtf));
publicTerminal->_ClearSelection();
return 0;
Expand Down
7 changes: 4 additions & 3 deletions src/cascadia/TerminalControl/TermControl.cpp
Expand Up @@ -2217,16 +2217,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Arguments:
// - dismissSelection: dismiss the text selection after copy
// - singleLine: collapse all of the text to one line
// - withControlSequences: if enabled, the copied plain text contains color/style ANSI escape codes from the selection
// - formats: which formats to copy (defined by action's CopyFormatting arg). nullptr
// if we should defer which formats are copied to the global setting
bool TermControl::CopySelectionToClipboard(bool dismissSelection, bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats)
bool TermControl::CopySelectionToClipboard(bool dismissSelection, bool singleLine, bool withControlSequences, const Windows::Foundation::IReference<CopyFormat>& formats)
{
if (_IsClosing())
{
return false;
}

const auto successfulCopy = _interactivity.CopySelectionToClipboard(singleLine, formats);
const auto successfulCopy = _interactivity.CopySelectionToClipboard(singleLine, withControlSequences, formats);

if (dismissSelection)
{
Expand Down Expand Up @@ -3697,7 +3698,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const IInspectable& /*args*/)
{
// formats = nullptr -> copy all formats
_interactivity.CopySelectionToClipboard(false, nullptr);
_interactivity.CopySelectionToClipboard(false, false, nullptr);
ContextMenu().Hide();
SelectionContextMenu().Hide();
}
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalControl/TermControl.h
Expand Up @@ -38,7 +38,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation

hstring GetProfileName() const;

bool CopySelectionToClipboard(bool dismissSelection, bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats);
bool CopySelectionToClipboard(bool dismissSelection, bool singleLine, bool withControlSequences, const Windows::Foundation::IReference<CopyFormat>& formats);
void PasteTextFromClipboard();
void SelectAll();
bool ToggleBlockSelection();
Expand Down