Skip to content

Commit

Permalink
Add copyFormatting keybinding arg and array support (#6004)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
carlos-zamora authored Aug 15, 2020
1 parent e9a7053 commit 24b8c13
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 33 deletions.
4 changes: 2 additions & 2 deletions doc/cascadia/SettingsSchema.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down Expand Up @@ -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. |
Expand Down
42 changes: 40 additions & 2 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
}
]
}
}
}
Expand Down Expand Up @@ -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,
Expand Down
42 changes: 40 additions & 2 deletions src/cascadia/TerminalApp/ActionArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

#include <LibraryResources.h>

using namespace winrt::Microsoft::Terminal::TerminalControl;

namespace winrt::TerminalApp::implementation
{
winrt::hstring NewTerminalArgs::GenerateName() const
Expand Down Expand Up @@ -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();
}
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<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
Expand Down
6 changes: 5 additions & 1 deletion src/cascadia/TerminalApp/ActionArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
};
Expand All @@ -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, {} };
}
};
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/ActionArgs.idl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/AppActionHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/GlobalAppSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,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);
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/JsonUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 14 additions & 3 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1676,18 +1676,28 @@ 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();
if (!htmlData.empty())
{
dataPack.SetHtmlFormat(htmlData);
}
}

if (WI_IsFlagSet(copyFormats, CopyFormat::RTF))
{
// copy rtf data to dataPack
const auto rtfData = copiedData.Rtf();
if (!rtfData.empty())
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
24 changes: 24 additions & 0 deletions src/cascadia/TerminalApp/TerminalSettingsSerializationHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
37 changes: 22 additions & 15 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -1152,7 +1154,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
else
{
CopySelectionToClipboard(shiftEnabled);
CopySelectionToClipboard(shiftEnabled, nullptr);
}
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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())
{
Expand All @@ -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;
}
Expand Down
Loading

0 comments on commit 24b8c13

Please sign in to comment.