Add copyFormatting keybinding arg and array support (#6004)

Adds array support for the existing `copyFormatting` global setting.
This allows users to define which formats they would specifically like
to be copied.

A boolean value is still accepted and is translated to the following:
- `false` --> `"none"` or `[]`
- `true` --> `"all"` or `["html", "rtf"]`

This also adds `copyFormatting` as a keybinding arg for `copy`. As with
the global setting, a boolean value and array value is accepted.

CopyFormat is a WinRT enum where each accepted format is a flag.
Currently accepted formats include `html`, and `rtf`. A boolean value is
accepted and converted. `true` is a conjunction of all the formats.
`false` only includes plain text.

For the global setting, `null` is not accepted. We already have a
default value from before so no worries there.

For the keybinding arg, `null` (the default value) means that we just do
what the global arg says to do. Overall, the `copyFormatting` keybinding
arg is an override of the global setting **when using that keybinding**.

References #5212 - Spec for formatted copying
References #2690 - disable html copy

Validated behavior with every combination of values below:
- `copyFormatting` global: { `true`, `false`, `[]`, `["html"]` }
- `copyFormatting` copy arg:
  { `null`, `true`, `false`, `[]`, `[, "html"]`}

Closes #4191
Closes #5262
This commit is contained in:
Carlos Zamora 2020-08-14 18:02:24 -07:00 committed by GitHub
parent e9a7053629
commit 24b8c13bd0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 174 additions and 33 deletions

View file

@ -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 <kbd>Ctrl</kbd> + <kbd>T</kbd>. |
| `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 <kbd>Ctrl</kbd> + <kbd>T</kbd> 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`<br>2. `copyFormatting` | 1. boolean<br>2. boolean, array | 1. When `true`, the copied content will be copied as a single line. When `false`, newlines persist from the selected text.<br>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. |

View file

@ -90,6 +90,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",
@ -163,6 +189,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"
}
]
}
}
}
@ -468,8 +506,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,

View file

@ -26,6 +26,8 @@
#include <LibraryResources.h>
using namespace winrt::Microsoft::Terminal::TerminalControl;
namespace winrt::TerminalApp::implementation
{
winrt::hstring NewTerminalArgs::GenerateName() const
@ -67,11 +69,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();
}
return RS_(L"CopyTextCommandKey");
else
{
ss << RS_(L"CopyTextCommandKey").c_str();
}
if (_CopyFormatting != nullptr)
{
ss << L", copyFormatting: ";
if (_CopyFormatting.Value() == CopyFormat::All)
{
ss << L"all, ";
}
else if (_CopyFormatting.Value() == static_cast<CopyFormat>(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

View file

@ -95,8 +95,10 @@ namespace winrt::TerminalApp::implementation
{
CopyTextArgs() = default;
GETSET_PROPERTY(bool, SingleLine, false);
GETSET_PROPERTY(Windows::Foundation::IReference<Microsoft::Terminal::TerminalControl::CopyFormat>, CopyFormatting, nullptr);
static constexpr std::string_view SingleLineKey{ "singleLine" };
static constexpr std::string_view CopyFormattingKey{ "copyFormatting" };
public:
hstring GenerateName() const;
@ -106,7 +108,8 @@ namespace winrt::TerminalApp::implementation
auto otherAsUs = other.try_as<CopyTextArgs>();
if (otherAsUs)
{
return otherAsUs->_SingleLine == _SingleLine;
return otherAsUs->_SingleLine == _SingleLine &&
otherAsUs->_CopyFormatting == _CopyFormatting;
}
return false;
};
@ -115,6 +118,7 @@ namespace winrt::TerminalApp::implementation
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<CopyTextArgs>();
JsonUtils::GetValueForKey(json, SingleLineKey, args->_SingleLine);
JsonUtils::GetValueForKey(json, CopyFormattingKey, args->_CopyFormatting);
return { *args, {} };
}
};

View file

@ -67,6 +67,7 @@ namespace TerminalApp
[default_interface] runtimeclass CopyTextArgs : IActionArgs
{
Boolean SingleLine { get; };
Windows.Foundation.IReference<Microsoft.Terminal.TerminalControl.CopyFormat> CopyFormatting { get; };
};
[default_interface] runtimeclass NewTabArgs : IActionArgs

View file

@ -237,7 +237,7 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::CopyTextArgs>())
{
const auto handled = _CopyText(realArgs.SingleLine());
const auto handled = _CopyText(realArgs.SingleLine(), realArgs.CopyFormatting());
args.Handled(handled);
}
}

View file

@ -70,7 +70,7 @@ public:
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);

View file

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

View file

@ -1676,10 +1676,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();
@ -1687,7 +1694,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())
@ -1780,12 +1790,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<CopyFormat>& formats)
{
const auto control = _GetActiveControl();
return control.CopySelectionToClipboard(singleLine);
return control.CopySelectionToClipboard(singleLine, formats);
}
// Method Description:

View file

@ -170,7 +170,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<Microsoft::Terminal::TerminalControl::CopyFormat>& formats);
void _PasteText();
fire_and_forget _LaunchSettings(const winrt::TerminalApp::SettingsTarget target);

View file

@ -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,

View file

@ -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.
@ -1152,7 +1154,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
else
{
CopySelectionToClipboard(shiftEnabled);
CopySelectionToClipboard(shiftEnabled, nullptr);
}
}
}
@ -1312,7 +1314,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)
@ -2088,9 +2090,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void TermControl::_CopyToClipboard(const std::wstring_view& wstr)
{
auto copyArgs = winrt::make_self<CopyToClipboardEventArgs>(winrt::hstring(wstr),
winrt::hstring(L""),
winrt::hstring(L""));
auto copyArgs = winrt::make_self<CopyToClipboardEventArgs>(winrt::hstring(wstr));
_clipboardCopyHandlers(*this, *copyArgs);
}
@ -2154,7 +2154,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<CopyFormat>& formats)
{
if (_closing)
{
@ -2184,16 +2186,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())
{
@ -2204,7 +2210,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// send data up for clipboard
auto copyArgs = winrt::make_self<CopyToClipboardEventArgs>(winrt::hstring(textData),
winrt::to_hstring(htmlData),
winrt::to_hstring(rtfData));
winrt::to_hstring(rtfData),
formats);
_clipboardCopyHandlers(*this, *copyArgs);
return true;
}

View file

@ -27,19 +27,28 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
public CopyToClipboardEventArgsT<CopyToClipboardEventArgs>
{
public:
CopyToClipboardEventArgs(hstring text, hstring html, hstring rtf) :
CopyToClipboardEventArgs(hstring text) :
_text(text),
_html(),
_rtf(),
_formats(static_cast<CopyFormat>(0)) {}
CopyToClipboardEventArgs(hstring text, hstring html, hstring rtf, Windows::Foundation::IReference<CopyFormat> 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<CopyFormat> Formats() { return _formats; };
private:
hstring _text;
hstring _html;
hstring _rtf;
Windows::Foundation::IReference<CopyFormat> _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<CopyFormat>& formats);
void PasteTextFromClipboard();
void Close();
Windows::Foundation::Size CharacterDimensions() const;

View file

@ -19,11 +19,20 @@ namespace Microsoft.Terminal.TerminalControl
Boolean OnDirectKeyEvent(UInt32 vkey, UInt8 scanCode, 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<CopyFormat> 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<CopyFormat> formats);
void PasteTextFromClipboard();
void Close();
Windows.Foundation.Size CharacterDimensions { get; };