diff --git a/doc/cascadia/SettingsSchema.md b/doc/cascadia/SettingsSchema.md index 9c58edc89f4..4df5ee03aa2 100644 --- a/doc/cascadia/SettingsSchema.md +++ b/doc/cascadia/SettingsSchema.md @@ -8,7 +8,7 @@ Properties listed below affect the entire window, regardless of the profile sett | -------- | --------- | ---- | ------- | ----------- | | `alwaysShowTabs` | _Required_ | Boolean | `true` | When set to `true`, tabs are always displayed. When set to `false` and `showTabsInTitlebar` is set to `false`, tabs only appear after typing Ctrl + T. | | `copyOnSelect` | Optional | Boolean | `false` | When set to `true`, a selection is immediately copied to your clipboard upon creation. When set to `false`, the selection persists and awaits further action. | -| `copyFormatting` | Optional | Boolean | `false` | When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. | +| `copyFormatting` | Optional | Boolean, Array | `true` | When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied. | | `largePasteWarning` | Optional | Boolean | `true` | When set to `true`, trying to paste text with more than 5 KiB of characters will display a warning asking you whether to continue or not with the paste. | | `multiLinePasteWarning` | Optional | Boolean | `true` | When set to `true`, trying to paste text with a _new line_ character will display a warning asking you whether to continue or not with the paste. | | `defaultProfile` | _Required_ | String | PowerShell guid | Sets the default profile. Opens by typing Ctrl + T or by clicking the '+' icon. The guid of the desired default profile is used as the value. | @@ -126,7 +126,7 @@ For commands with arguments: | `closePane` | Close the active pane. | | | | | `closeTab` | Close the current tab. | | | | | `closeWindow` | Close the current window and all tabs within it. | | | | -| `copy` | Copy the selected terminal content to your Windows Clipboard. | `singleLine` | boolean | When `true`, the copied content will be copied as a single line. When `false`, newlines persist from the selected text. | +| `copy` | Copy the selected terminal content to your Windows Clipboard. | 1. `singleLine`
2. `copyFormatting` | 1. boolean
2. boolean, array | 1. When `true`, the copied content will be copied as a single line. When `false`, newlines persist from the selected text.
2. When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied. Not setting this value inherits the behavior of the `copyFormatting` global setting. | | `duplicateTab` | Make a copy and open the current tab. | | | | | `find` | Open the search dialog box. | | | | | `moveFocus` | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. | diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index f1e1843bf96..1a473de8983 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -89,6 +89,32 @@ ], "type": "string" }, + "CopyFormat": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "html", + "rtf" + ] + } + }, + { + "type": "string", + "enum": [ + "html", + "rtf", + "all", + "none" + ] + } + ] + }, "AnchorKey": { "enum": [ "ctrl", @@ -162,6 +188,18 @@ "type": "boolean", "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." + }, + "copyFormatting": { + "default": null, + "description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied. Not setting this value inherits the behavior of the `copyFormatting` global setting.", + "oneOf": [ + { + "$ref": "#/definitions/CopyFormat" + }, + { + "type": "null" + } + ] } } } @@ -444,8 +482,8 @@ }, "copyFormatting": { "default": true, - "description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard.", - "type": "boolean" + "description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.", + "$ref": "#/definitions/CopyFormat" }, "largePasteWarning": { "default": true, diff --git a/src/cascadia/TerminalApp/ActionArgs.cpp b/src/cascadia/TerminalApp/ActionArgs.cpp index d6e7219fd13..3830d9c0963 100644 --- a/src/cascadia/TerminalApp/ActionArgs.cpp +++ b/src/cascadia/TerminalApp/ActionArgs.cpp @@ -23,6 +23,8 @@ #include +using namespace winrt::Microsoft::Terminal::TerminalControl; + namespace winrt::TerminalApp::implementation { winrt::hstring NewTerminalArgs::GenerateName() const @@ -64,11 +66,47 @@ namespace winrt::TerminalApp::implementation winrt::hstring CopyTextArgs::GenerateName() const { + std::wstringstream ss; + if (_SingleLine) { - return RS_(L"CopyTextAsSingleLineCommandKey"); + ss << RS_(L"CopyTextAsSingleLineCommandKey").c_str(); + } + else + { + ss << RS_(L"CopyTextCommandKey").c_str(); } - return RS_(L"CopyTextCommandKey"); + + if (_CopyFormatting != nullptr) + { + ss << L", copyFormatting: "; + if (_CopyFormatting.Value() == CopyFormat::All) + { + ss << L"all, "; + } + else if (_CopyFormatting.Value() == static_cast(0)) + { + ss << L"none, "; + } + else + { + if (WI_IsFlagSet(_CopyFormatting.Value(), CopyFormat::HTML)) + { + ss << L"html, "; + } + + if (WI_IsFlagSet(_CopyFormatting.Value(), CopyFormat::RTF)) + { + ss << L"rtf, "; + } + } + + // Chop off the last "," + auto result = ss.str(); + return winrt::hstring{ result.substr(0, result.size() - 2) }; + } + + return winrt::hstring{ ss.str() }; } winrt::hstring NewTabArgs::GenerateName() const diff --git a/src/cascadia/TerminalApp/ActionArgs.h b/src/cascadia/TerminalApp/ActionArgs.h index 7c768d58054..c78cb68b539 100644 --- a/src/cascadia/TerminalApp/ActionArgs.h +++ b/src/cascadia/TerminalApp/ActionArgs.h @@ -94,8 +94,10 @@ namespace winrt::TerminalApp::implementation { CopyTextArgs() = default; GETSET_PROPERTY(bool, SingleLine, false); + GETSET_PROPERTY(Windows::Foundation::IReference, CopyFormatting, nullptr); static constexpr std::string_view SingleLineKey{ "singleLine" }; + static constexpr std::string_view CopyFormattingKey{ "copyFormatting" }; public: hstring GenerateName() const; @@ -105,7 +107,8 @@ namespace winrt::TerminalApp::implementation auto otherAsUs = other.try_as(); if (otherAsUs) { - return otherAsUs->_SingleLine == _SingleLine; + return otherAsUs->_SingleLine == _SingleLine && + otherAsUs->_CopyFormatting == _CopyFormatting; } return false; }; @@ -114,6 +117,7 @@ namespace winrt::TerminalApp::implementation // LOAD BEARING: Not using make_self here _will_ break you in the future! auto args = winrt::make_self(); JsonUtils::GetValueForKey(json, SingleLineKey, args->_SingleLine); + JsonUtils::GetValueForKey(json, CopyFormattingKey, args->_CopyFormatting); return { *args, {} }; } }; diff --git a/src/cascadia/TerminalApp/ActionArgs.idl b/src/cascadia/TerminalApp/ActionArgs.idl index a460d712a92..a0273b3cbb3 100644 --- a/src/cascadia/TerminalApp/ActionArgs.idl +++ b/src/cascadia/TerminalApp/ActionArgs.idl @@ -67,6 +67,7 @@ namespace TerminalApp [default_interface] runtimeclass CopyTextArgs : IActionArgs { Boolean SingleLine { get; }; + Windows.Foundation.IReference CopyFormatting { get; }; }; [default_interface] runtimeclass NewTabArgs : IActionArgs diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 19a8c44f2ad..b98be083f79 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -220,7 +220,7 @@ namespace winrt::TerminalApp::implementation { if (const auto& realArgs = args.ActionArgs().try_as()) { - const auto handled = _CopyText(realArgs.SingleLine()); + const auto handled = _CopyText(realArgs.SingleLine(), realArgs.CopyFormatting()); args.Handled(handled); } } diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.h b/src/cascadia/TerminalApp/GlobalAppSettings.h index ba51109fa0b..b7ed2c14f4b 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.h +++ b/src/cascadia/TerminalApp/GlobalAppSettings.h @@ -68,7 +68,7 @@ class TerminalApp::GlobalAppSettings final GETSET_PROPERTY(bool, ShowTabsInTitlebar, true); GETSET_PROPERTY(std::wstring, WordDelimiters); // default value set in constructor GETSET_PROPERTY(bool, CopyOnSelect, false); - GETSET_PROPERTY(bool, CopyFormatting, false); + GETSET_PROPERTY(winrt::Microsoft::Terminal::TerminalControl::CopyFormat, CopyFormatting, 0); GETSET_PROPERTY(bool, WarnAboutLargePaste, true); GETSET_PROPERTY(bool, WarnAboutMultiLinePaste, true); GETSET_PROPERTY(LaunchPosition, InitialPosition); diff --git a/src/cascadia/TerminalApp/JsonUtils.h b/src/cascadia/TerminalApp/JsonUtils.h index b15eb1478a2..b75c58200e1 100644 --- a/src/cascadia/TerminalApp/JsonUtils.h +++ b/src/cascadia/TerminalApp/JsonUtils.h @@ -372,7 +372,7 @@ namespace TerminalApp::JsonUtils { // attempt to combine AllClear (explicitly) with anything else DeserializationError e{ element }; - e.expectedType = TypeDescription(); + e.expectedType = BaseEnumMapper::TypeDescription(); throw e; } value |= newFlag; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 3838e4ccf50..b4319175408 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1650,10 +1650,17 @@ namespace winrt::TerminalApp::implementation DataPackage dataPack = DataPackage(); dataPack.RequestedOperation(DataPackageOperation::Copy); + // The EventArgs.Formats() is an override for the global setting "copyFormatting" + // iff it is set + bool useGlobal = copiedData.Formats() == nullptr; + auto copyFormats = useGlobal ? + _settings->GlobalSettings().CopyFormatting() : + copiedData.Formats().Value(); + // copy text to dataPack dataPack.SetText(copiedData.Text()); - if (_settings->GlobalSettings().CopyFormatting()) + if (WI_IsFlagSet(copyFormats, CopyFormat::HTML)) { // copy html to dataPack const auto htmlData = copiedData.Html(); @@ -1661,7 +1668,10 @@ namespace winrt::TerminalApp::implementation { dataPack.SetHtmlFormat(htmlData); } + } + if (WI_IsFlagSet(copyFormats, CopyFormat::RTF)) + { // copy rtf data to dataPack const auto rtfData = copiedData.Rtf(); if (!rtfData.empty()) @@ -1754,12 +1764,13 @@ namespace winrt::TerminalApp::implementation // - Copy text from the focused terminal to the Windows Clipboard // Arguments: // - singleLine: if enabled, copy contents as a single line of text + // - 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 singleLine) + bool TerminalPage::_CopyText(const bool singleLine, const Windows::Foundation::IReference& formats) { const auto control = _GetActiveControl(); - return control.CopySelectionToClipboard(singleLine); + return control.CopySelectionToClipboard(singleLine, formats); } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 984f1064245..8b1af8e8d93 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -165,7 +165,7 @@ namespace winrt::TerminalApp::implementation winrt::fire_and_forget _CopyToClipboardHandler(const IInspectable sender, const winrt::Microsoft::Terminal::TerminalControl::CopyToClipboardEventArgs copiedData); winrt::fire_and_forget _PasteFromClipboardHandler(const IInspectable sender, const Microsoft::Terminal::TerminalControl::PasteFromClipboardEventArgs eventArgs); - bool _CopyText(const bool trimTrailingWhitespace); + bool _CopyText(const bool singleLine, const Windows::Foundation::IReference& formats); void _PasteText(); fire_and_forget _LaunchSettings(const winrt::TerminalApp::SettingsTarget target); diff --git a/src/cascadia/TerminalApp/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalApp/TerminalSettingsSerializationHelpers.h index c1e60b6a705..c83d0ae1298 100644 --- a/src/cascadia/TerminalApp/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalApp/TerminalSettingsSerializationHelpers.h @@ -184,6 +184,30 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode) }; }; +JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::TerminalControl::CopyFormat) +{ + JSON_MAPPINGS(5) = { + pair_type{ "none", AllClear }, + pair_type{ "html", ValueType::HTML }, + pair_type{ "rtf", ValueType::RTF }, + pair_type{ "all", AllSet }, + }; + + auto FromJson(const Json::Value& json) + { + if (json.isBool()) + { + return json.asBool() ? AllSet : AllClear; + } + return BaseFlagMapper::FromJson(json); + } + + bool CanConvert(const Json::Value& json) + { + return BaseFlagMapper::CanConvert(json) || json.isBool(); + } +}; + // Type Description: // - Helper for converting the initial position string into // 2 coordinate values. We allow users to only provide one coordinate, diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 245413fe75c..df960d942bf 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -35,6 +35,8 @@ constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8); // The minimum delay between updating the TSF input control. constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100); +DEFINE_ENUM_FLAG_OPERATORS(winrt::Microsoft::Terminal::TerminalControl::CopyFormat); + namespace winrt::Microsoft::Terminal::TerminalControl::implementation { // Helper static function to ensure that all ambiguous-width glyphs are reported as narrow. @@ -1141,7 +1143,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } else { - CopySelectionToClipboard(shiftEnabled); + CopySelectionToClipboard(shiftEnabled, nullptr); } } } @@ -1301,7 +1303,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // Right clicks and middle clicks should not need to do anything when released. if (_settings.CopyOnSelect() && point.Properties().PointerUpdateKind() == Windows::UI::Input::PointerUpdateKind::LeftButtonReleased && _selectionNeedsToBeCopied) { - CopySelectionToClipboard(); + CopySelectionToClipboard(false, nullptr); } } else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch) @@ -2071,9 +2073,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void TermControl::_CopyToClipboard(const std::wstring_view& wstr) { - auto copyArgs = winrt::make_self(winrt::hstring(wstr), - winrt::hstring(L""), - winrt::hstring(L"")); + auto copyArgs = winrt::make_self(winrt::hstring(wstr)); _clipboardCopyHandlers(*this, *copyArgs); } @@ -2137,7 +2137,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - CopyOnSelect does NOT clear the selection // Arguments: // - singleLine: collapse all of the text to one line - bool TermControl::CopySelectionToClipboard(bool singleLine) + // - 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 singleLine, const Windows::Foundation::IReference& formats) { if (_closing) { @@ -2167,16 +2169,20 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // GH#5347 - Don't provide a title for the generated HTML, as many // web applications will paste the title first, followed by the HTML // content, which is unexpected. - const auto htmlData = TextBuffer::GenHTML(bufferData, - _actualFont.GetUnscaledSize().Y, - _actualFont.GetFaceName(), - _settings.DefaultBackground()); + const auto htmlData = formats == nullptr || WI_IsFlagSet(formats.Value(), CopyFormat::HTML) ? + TextBuffer::GenHTML(bufferData, + _actualFont.GetUnscaledSize().Y, + _actualFont.GetFaceName(), + _settings.DefaultBackground()) : + ""; // convert to RTF format - const auto rtfData = TextBuffer::GenRTF(bufferData, - _actualFont.GetUnscaledSize().Y, - _actualFont.GetFaceName(), - _settings.DefaultBackground()); + const auto rtfData = formats == nullptr || WI_IsFlagSet(formats.Value(), CopyFormat::RTF) ? + TextBuffer::GenRTF(bufferData, + _actualFont.GetUnscaledSize().Y, + _actualFont.GetFaceName(), + _settings.DefaultBackground()) : + ""; if (!_settings.CopyOnSelect()) { @@ -2187,7 +2193,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // send data up for clipboard auto copyArgs = winrt::make_self(winrt::hstring(textData), winrt::to_hstring(htmlData), - winrt::to_hstring(rtfData)); + winrt::to_hstring(rtfData), + formats); _clipboardCopyHandlers(*this, *copyArgs); return true; } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index a3f26712962..727d124d0f8 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -27,19 +27,28 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation public CopyToClipboardEventArgsT { public: - CopyToClipboardEventArgs(hstring text, hstring html, hstring rtf) : + CopyToClipboardEventArgs(hstring text) : + _text(text), + _html(), + _rtf(), + _formats(static_cast(0)) {} + + CopyToClipboardEventArgs(hstring text, hstring html, hstring rtf, Windows::Foundation::IReference formats) : _text(text), _html(html), - _rtf(rtf) {} + _rtf(rtf), + _formats(formats) {} hstring Text() { return _text; }; hstring Html() { return _html; }; hstring Rtf() { return _rtf; }; + Windows::Foundation::IReference Formats() { return _formats; }; private: hstring _text; hstring _html; hstring _rtf; + Windows::Foundation::IReference _formats; }; struct PasteFromClipboardEventArgs : @@ -67,7 +76,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation hstring Title(); hstring GetProfileName() const; - bool CopySelectionToClipboard(bool collapseText = false); + bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference& formats); void PasteTextFromClipboard(); void Close(); Windows::Foundation::Size CharacterDimensions() const; diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 85b6e602cfd..fe8ba7507a9 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -19,11 +19,20 @@ namespace Microsoft.Terminal.TerminalControl Boolean OnDirectKeyEvent(UInt32 vkey, Boolean down); } + [flags] + enum CopyFormat + { + HTML = 0x1, + RTF = 0x2, + All = 0xffffffff + }; + runtimeclass CopyToClipboardEventArgs { String Text { get; }; String Html { get; }; String Rtf { get; }; + Windows.Foundation.IReference Formats { get; }; } runtimeclass PasteFromClipboardEventArgs @@ -54,7 +63,7 @@ namespace Microsoft.Terminal.TerminalControl String Title { get; }; - Boolean CopySelectionToClipboard(Boolean collapseText); + Boolean CopySelectionToClipboard(Boolean singleLine, Windows.Foundation.IReference formats); void PasteTextFromClipboard(); void Close(); Windows.Foundation.Size CharacterDimensions { get; };