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; };