diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt
index fdb6a2dcd05..1e33da10d7e 100644
--- a/.github/actions/spell-check/dictionary/apis.txt
+++ b/.github/actions/spell-check/dictionary/apis.txt
@@ -1,11 +1,13 @@
ACCEPTFILES
ACCESSDENIED
+alignof
bitfield
bitfields
CLASSNOTAVAILABLE
EXPCMDFLAGS
EXPCMDSTATE
fullkbd
+futex
href
IAsync
IBind
@@ -19,7 +21,9 @@ IExplorer
IMap
IObject
IStorage
+llabs
LCID
+lround
LSHIFT
NCHITTEST
NCLBUTTONDBLCLK
@@ -28,10 +32,17 @@ NOAGGREGATION
NOREDIRECTIONBITMAP
oaidl
ocidl
+otms
+OUTLINETEXTMETRICW
PAGESCROLL
RETURNCMD
rfind
roundf
RSHIFT
+rx
SIZENS
+spsc
+STDCPP
+syscall
tmp
+tx
diff --git a/.github/actions/spell-check/patterns/patterns.txt b/.github/actions/spell-check/patterns/patterns.txt
index 413709e1202..f8c3d65534a 100644
--- a/.github/actions/spell-check/patterns/patterns.txt
+++ b/.github/actions/spell-check/patterns/patterns.txt
@@ -19,3 +19,4 @@ TestUtils::VerifyExpectedString\(tb, L"[^"]+"
Base64::s_(?:En|De)code\(L"[^"]+"
VERIFY_ARE_EQUAL\(L"[^"]+"
L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+/"
+std::memory_order_[\w]+
diff --git a/NuGet.Config b/NuGet.Config
index de105e187bc..00b1de60c4f 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -8,7 +8,7 @@
-
+
+
+[#1337]: https://github.com/microsoft/terminal/issues/1337
+[#3789]: https://github.com/microsoft/terminal/issues/3789
+[#3327]: https://github.com/microsoft/terminal/issues/3327
+[#5772]: https://github.com/microsoft/terminal/pull/5772
diff --git a/doc/specs/#1337 - Per-Profile Tab Colors/profile-tabColor-000.gif b/doc/specs/#1337 - Per-Profile Tab Colors/profile-tabColor-000.gif
new file mode 100644
index 00000000000..2b3e04a6513
Binary files /dev/null and b/doc/specs/#1337 - Per-Profile Tab Colors/profile-tabColor-000.gif differ
diff --git a/res/Cascadia.ttf b/res/Cascadia.ttf
index a271f0dd509..48acfd4e676 100644
Binary files a/res/Cascadia.ttf and b/res/Cascadia.ttf differ
diff --git a/res/CascadiaMono.ttf b/res/CascadiaMono.ttf
index 2d9145a88e8..e90a0f58b82 100644
Binary files a/res/CascadiaMono.ttf and b/res/CascadiaMono.ttf differ
diff --git a/res/README.md b/res/README.md
index 8ae4844d54c..f0929cfff73 100644
--- a/res/README.md
+++ b/res/README.md
@@ -17,5 +17,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
### Fonts Included
-* Cascadia Code, Cascadia Mono (2007.01)
- * from microsoft/cascadia-code@311cc603f30635da704b6a7d13050e245e61667b
+* Cascadia Code, Cascadia Mono (2007.15)
+ * from microsoft/cascadia-code@2a54363b2c867f7ae811b9a034c0024cef67de96
diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp
index 6c82b718fb9..2718aa14229 100644
--- a/src/buffer/out/Row.cpp
+++ b/src/buffer/out/Row.cpp
@@ -160,66 +160,98 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
// If we're given a right-side column limit, use it. Otherwise, the write limit is the final column index available in the char row.
const auto finalColumnInRow = limitRight.value_or(_charRow.size() - 1);
- while (it && currentIndex <= finalColumnInRow)
+ if (it)
{
- // Fill the color if the behavior isn't set to keeping the current color.
- if (it->TextAttrBehavior() != TextAttributeBehavior::Current)
- {
- const TextAttributeRun attrRun{ 1, it->TextAttr() };
- LOG_IF_FAILED(_attrRow.InsertAttrRuns({ &attrRun, 1 },
- currentIndex,
- currentIndex,
- _charRow.size()));
- }
+ // Accumulate usages of the same color so we can spend less time in InsertAttrRuns rewriting it.
+ auto currentColor = it->TextAttr();
+ size_t colorUses = 0;
+ size_t colorStarts = index;
- // Fill the text if the behavior isn't set to saying there's only a color stored in this iterator.
- if (it->TextAttrBehavior() != TextAttributeBehavior::StoredOnly)
+ while (it && currentIndex <= finalColumnInRow)
{
- const bool fillingLastColumn = currentIndex == finalColumnInRow;
-
- // TODO: MSFT: 19452170 - We need to ensure when writing any trailing byte that the one to the left
- // is a matching leading byte. Likewise, if we're writing a leading byte, we need to make sure we still have space in this loop
- // for the trailing byte coming up before writing it.
-
- // If we're trying to fill the first cell with a trailing byte, pad it out instead by clearing it.
- // Don't increment iterator. We'll advance the index and try again with this value on the next round through the loop.
- if (currentIndex == 0 && it->DbcsAttr().IsTrailing())
+ // Fill the color if the behavior isn't set to keeping the current color.
+ if (it->TextAttrBehavior() != TextAttributeBehavior::Current)
{
- _charRow.ClearCell(currentIndex);
+ // If the color of this cell is the same as the run we're currently on,
+ // just increment the counter.
+ if (currentColor == it->TextAttr())
+ {
+ ++colorUses;
+ }
+ else
+ {
+ // Otherwise, commit this color into the run and save off the new one.
+ const TextAttributeRun run{ colorUses, currentColor };
+ // Now commit the new color runs into the attr row.
+ LOG_IF_FAILED(_attrRow.InsertAttrRuns({ &run, 1 },
+ colorStarts,
+ currentIndex - 1,
+ _charRow.size()));
+ currentColor = it->TextAttr();
+ colorUses = 1;
+ colorStarts = currentIndex;
+ }
}
- // If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it.
- // Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line.
- else if (fillingLastColumn && it->DbcsAttr().IsLeading())
+
+ // Fill the text if the behavior isn't set to saying there's only a color stored in this iterator.
+ if (it->TextAttrBehavior() != TextAttributeBehavior::StoredOnly)
{
- _charRow.ClearCell(currentIndex);
- _charRow.SetDoubleBytePadded(true);
+ const bool fillingLastColumn = currentIndex == finalColumnInRow;
+
+ // TODO: MSFT: 19452170 - We need to ensure when writing any trailing byte that the one to the left
+ // is a matching leading byte. Likewise, if we're writing a leading byte, we need to make sure we still have space in this loop
+ // for the trailing byte coming up before writing it.
+
+ // If we're trying to fill the first cell with a trailing byte, pad it out instead by clearing it.
+ // Don't increment iterator. We'll advance the index and try again with this value on the next round through the loop.
+ if (currentIndex == 0 && it->DbcsAttr().IsTrailing())
+ {
+ _charRow.ClearCell(currentIndex);
+ }
+ // If we're trying to fill the last cell with a leading byte, pad it out instead by clearing it.
+ // Don't increment iterator. We'll exit because we couldn't write a lead at the end of a line.
+ else if (fillingLastColumn && it->DbcsAttr().IsLeading())
+ {
+ _charRow.ClearCell(currentIndex);
+ _charRow.SetDoubleBytePadded(true);
+ }
+ // Otherwise, copy the data given and increment the iterator.
+ else
+ {
+ _charRow.DbcsAttrAt(currentIndex) = it->DbcsAttr();
+ _charRow.GlyphAt(currentIndex) = it->Chars();
+ ++it;
+ }
+
+ // If we're asked to (un)set the wrap status and we just filled the last column with some text...
+ // NOTE:
+ // - wrap = std::nullopt --> don't change the wrap value
+ // - wrap = true --> we're filling cells as a steam, consider this a wrap
+ // - wrap = false --> we're filling cells as a block, unwrap
+ if (wrap.has_value() && fillingLastColumn)
+ {
+ // set wrap status on the row to parameter's value.
+ _charRow.SetWrapForced(wrap.value());
+ }
}
- // Otherwise, copy the data given and increment the iterator.
else
{
- _charRow.DbcsAttrAt(currentIndex) = it->DbcsAttr();
- _charRow.GlyphAt(currentIndex) = it->Chars();
++it;
}
- // If we're asked to (un)set the wrap status and we just filled the last column with some text...
- // NOTE:
- // - wrap = std::nullopt --> don't change the wrap value
- // - wrap = true --> we're filling cells as a steam, consider this a wrap
- // - wrap = false --> we're filling cells as a block, unwrap
- if (wrap.has_value() && fillingLastColumn)
- {
- // set wrap status on the row to parameter's value.
- _charRow.SetWrapForced(wrap.value());
- }
+ // Move to the next cell for the next time through the loop.
+ ++currentIndex;
}
- else
+
+ // Now commit the final color into the attr row
+ if (colorUses)
{
- ++it;
+ const TextAttributeRun run{ colorUses, currentColor };
+ LOG_IF_FAILED(_attrRow.InsertAttrRuns({ &run, 1 },
+ colorStarts,
+ currentIndex - 1,
+ _charRow.size()));
}
-
- // Move to the next cell for the next time through the loop.
- ++currentIndex;
}
return it;
diff --git a/src/buffer/out/TextAttribute.cpp b/src/buffer/out/TextAttribute.cpp
index b58975f9a85..ad7ee0546e7 100644
--- a/src/buffer/out/TextAttribute.cpp
+++ b/src/buffer/out/TextAttribute.cpp
@@ -246,8 +246,7 @@ bool TextAttribute::IsCrossedOut() const noexcept
bool TextAttribute::IsUnderlined() const noexcept
{
- // TODO:GH#2915 Treat underline separately from LVB_UNDERSCORE
- return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_UNDERSCORE);
+ return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Underlined);
}
bool TextAttribute::IsOverlined() const noexcept
@@ -270,7 +269,7 @@ void TextAttribute::SetFaint(bool isFaint) noexcept
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Faint, isFaint);
}
-void TextAttribute::SetItalics(bool isItalic) noexcept
+void TextAttribute::SetItalic(bool isItalic) noexcept
{
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Italics, isItalic);
}
@@ -290,13 +289,12 @@ void TextAttribute::SetCrossedOut(bool isCrossedOut) noexcept
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::CrossedOut, isCrossedOut);
}
-void TextAttribute::SetUnderline(bool isUnderlined) noexcept
+void TextAttribute::SetUnderlined(bool isUnderlined) noexcept
{
- // TODO:GH#2915 Treat underline separately from LVB_UNDERSCORE
- WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_UNDERSCORE, isUnderlined);
+ WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Underlined, isUnderlined);
}
-void TextAttribute::SetOverline(bool isOverlined) noexcept
+void TextAttribute::SetOverlined(bool isOverlined) noexcept
{
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL, isOverlined);
}
diff --git a/src/buffer/out/TextAttribute.hpp b/src/buffer/out/TextAttribute.hpp
index cccd9aaff7c..7cec03f75e6 100644
--- a/src/buffer/out/TextAttribute.hpp
+++ b/src/buffer/out/TextAttribute.hpp
@@ -100,12 +100,12 @@ class TextAttribute final
void SetBold(bool isBold) noexcept;
void SetFaint(bool isFaint) noexcept;
- void SetItalics(bool isItalic) noexcept;
+ void SetItalic(bool isItalic) noexcept;
void SetBlinking(bool isBlinking) noexcept;
void SetInvisible(bool isInvisible) noexcept;
void SetCrossedOut(bool isCrossedOut) noexcept;
- void SetUnderline(bool isUnderlined) noexcept;
- void SetOverline(bool isOverlined) noexcept;
+ void SetUnderlined(bool isUnderlined) noexcept;
+ void SetOverlined(bool isOverlined) noexcept;
void SetReverseVideo(bool isReversed) noexcept;
ExtendedAttributes GetExtendedAttributes() const noexcept;
@@ -218,11 +218,12 @@ namespace WEX
static WEX::Common::NoThrowString ToString(const TextAttribute& attr)
{
return WEX::Common::NoThrowString().Format(
- L"{FG:%s,BG:%s,bold:%d,wLegacy:(0x%04x)}",
+ L"{FG:%s,BG:%s,bold:%d,wLegacy:(0x%04x),ext:(0x%02x)}",
VerifyOutputTraits::ToString(attr._foreground).GetBuffer(),
VerifyOutputTraits::ToString(attr._background).GetBuffer(),
attr.IsBold(),
- attr._wAttrLegacy);
+ attr._wAttrLegacy,
+ static_cast(attr._extendedAttrs));
}
};
}
diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp
index faee76caf0e..6ecb989f2f0 100644
--- a/src/buffer/out/textBuffer.cpp
+++ b/src/buffer/out/textBuffer.cpp
@@ -787,7 +787,7 @@ const Cursor& TextBuffer::GetCursor() const noexcept
return _currentAttributes;
}
-void TextBuffer::SetCurrentAttributes(const TextAttribute currentAttributes) noexcept
+void TextBuffer::SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept
{
_currentAttributes = currentAttributes;
}
diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp
index 269bb6911e6..9ed2ce8b1af 100644
--- a/src/buffer/out/textBuffer.hpp
+++ b/src/buffer/out/textBuffer.hpp
@@ -118,7 +118,7 @@ class TextBuffer final
[[nodiscard]] TextAttribute GetCurrentAttributes() const noexcept;
- void SetCurrentAttributes(const TextAttribute currentAttributes) noexcept;
+ void SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept;
void Reset();
diff --git a/src/cascadia/LocalTests_TerminalApp/CommandTests.cpp b/src/cascadia/LocalTests_TerminalApp/CommandTests.cpp
index 129e2469182..1babcf6a2c8 100644
--- a/src/cascadia/LocalTests_TerminalApp/CommandTests.cpp
+++ b/src/cascadia/LocalTests_TerminalApp/CommandTests.cpp
@@ -147,10 +147,8 @@ namespace TerminalAppLocalTests
{ "name": "command0", "command": { "action": "splitPane", "split": null } },
{ "name": "command1", "command": { "action": "splitPane", "split": "vertical" } },
{ "name": "command2", "command": { "action": "splitPane", "split": "horizontal" } },
- { "name": "command3", "command": { "action": "splitPane", "split": "none" } },
{ "name": "command4", "command": { "action": "splitPane" } },
- { "name": "command5", "command": { "action": "splitPane", "split": "auto" } },
- { "name": "command6", "command": { "action": "splitPane", "split": "foo" } }
+ { "name": "command5", "command": { "action": "splitPane", "split": "auto" } }
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
@@ -159,7 +157,7 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(0u, commands.size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
- VERIFY_ARE_EQUAL(7u, commands.size());
+ VERIFY_ARE_EQUAL(5u, commands.size());
{
auto command = commands.at(L"command0");
@@ -191,16 +189,6 @@ namespace TerminalAppLocalTests
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
}
- {
- auto command = commands.at(L"command3");
- VERIFY_IS_NOT_NULL(command);
- VERIFY_IS_NOT_NULL(command.Action());
- VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
- const auto& realArgs = command.Action().Args().try_as();
- VERIFY_IS_NOT_NULL(realArgs);
- // Verify the args have the expected value
- VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
- }
{
auto command = commands.at(L"command4");
VERIFY_IS_NOT_NULL(command);
@@ -221,16 +209,6 @@ namespace TerminalAppLocalTests
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
- {
- auto command = commands.at(L"command6");
- VERIFY_IS_NOT_NULL(command);
- VERIFY_IS_NOT_NULL(command.Action());
- VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
- const auto& realArgs = command.Action().Args().try_as();
- VERIFY_IS_NOT_NULL(realArgs);
- // Verify the args have the expected value
- VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
- }
}
void CommandTests::TestResourceKeyName()
{
diff --git a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp
index e9191a4f2f5..d9735e2ccd4 100644
--- a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp
+++ b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp
@@ -4,7 +4,9 @@
#include "pch.h"
#include
+#include "../TerminalApp/TerminalPage.h"
#include "../TerminalApp/AppCommandlineArgs.h"
+#include "../TerminalApp/ActionArgs.h"
using namespace WEX::Logging;
using namespace WEX::Common;
@@ -52,6 +54,10 @@ namespace TerminalAppLocalTests
TEST_METHOD(CheckTypos);
+ TEST_METHOD(TestSimpleExecuteCommandlineAction);
+ TEST_METHOD(TestMultipleCommandExecuteCommandlineAction);
+ TEST_METHOD(TestInvalidExecuteCommandlineAction);
+
private:
void _buildCommandlinesHelper(AppCommandlineArgs& appArgs,
const size_t expectedSubcommands,
@@ -1067,4 +1073,66 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(L"C:\\", myArgs.TerminalArgs().StartingDirectory());
}
}
+
+ void CommandlineTest::TestSimpleExecuteCommandlineAction()
+ {
+ auto args = winrt::make_self();
+ args->Commandline(L"new-tab");
+ auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args);
+ VERIFY_ARE_EQUAL(1u, actions.size());
+ auto actionAndArgs = actions.at(0);
+ VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
+ VERIFY_IS_NOT_NULL(actionAndArgs.Args());
+ auto myArgs = actionAndArgs.Args().try_as();
+ VERIFY_IS_NOT_NULL(myArgs);
+ VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
+ }
+
+ void CommandlineTest::TestMultipleCommandExecuteCommandlineAction()
+ {
+ auto args = winrt::make_self();
+ args->Commandline(L"new-tab ; split-pane");
+ auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args);
+ VERIFY_ARE_EQUAL(2u, actions.size());
+ {
+ auto actionAndArgs = actions.at(0);
+ VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
+ VERIFY_IS_NOT_NULL(actionAndArgs.Args());
+ auto myArgs = actionAndArgs.Args().try_as();
+ VERIFY_IS_NOT_NULL(myArgs);
+ VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
+ }
+ {
+ auto actionAndArgs = actions.at(1);
+ VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
+ VERIFY_IS_NOT_NULL(actionAndArgs.Args());
+ auto myArgs = actionAndArgs.Args().try_as();
+ VERIFY_IS_NOT_NULL(myArgs);
+ VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
+ VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
+ }
+ }
+
+ void CommandlineTest::TestInvalidExecuteCommandlineAction()
+ {
+ auto args = winrt::make_self();
+ // -H and -V cannot be combined.
+ args->Commandline(L"split-pane -H -V");
+ auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args);
+ VERIFY_ARE_EQUAL(0u, actions.size());
+ }
}
diff --git a/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp
index 760bfd94c8a..ad51cf8269b 100644
--- a/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp
+++ b/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp
@@ -323,10 +323,8 @@ namespace TerminalAppLocalTests
{ "keys": ["ctrl+c"], "command": { "action": "splitPane", "split": null } },
{ "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal" } },
- { "keys": ["ctrl+f"], "command": { "action": "splitPane", "split": "none" } },
{ "keys": ["ctrl+g"], "command": { "action": "splitPane" } },
- { "keys": ["ctrl+h"], "command": { "action": "splitPane", "split": "auto" } },
- { "keys": ["ctrl+i"], "command": { "action": "splitPane", "split": "foo" } }
+ { "keys": ["ctrl+h"], "command": { "action": "splitPane", "split": "auto" } }
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
@@ -335,7 +333,7 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
- VERIFY_ARE_EQUAL(7u, appKeyBindings->_keyShortcuts.size());
+ VERIFY_ARE_EQUAL(5u, appKeyBindings->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast('C') };
@@ -364,15 +362,6 @@ namespace TerminalAppLocalTests
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
}
- {
- KeyChord kc{ true, false, false, static_cast('F') };
- auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
- VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
- const auto& realArgs = actionAndArgs.Args().try_as();
- VERIFY_IS_NOT_NULL(realArgs);
- // Verify the args have the expected value
- VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
- }
{
KeyChord kc{ true, false, false, static_cast('G') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
@@ -391,15 +380,6 @@ namespace TerminalAppLocalTests
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
- {
- KeyChord kc{ true, false, false, static_cast('I') };
- auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
- VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
- const auto& realArgs = actionAndArgs.Args().try_as();
- VERIFY_IS_NOT_NULL(realArgs);
- // Verify the args have the expected value
- VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
- }
}
void KeyBindingsTests::TestSetTabColorArgs()
@@ -407,7 +387,6 @@ namespace TerminalAppLocalTests
const std::string bindings0String{ R"([
{ "keys": ["ctrl+c"], "command": { "action": "setTabColor", "color": null } },
{ "keys": ["ctrl+d"], "command": { "action": "setTabColor", "color": "#123456" } },
- { "keys": ["ctrl+e"], "command": { "action": "setTabColor", "color": "thisStringObviouslyWontWork" } },
{ "keys": ["ctrl+f"], "command": "setTabColor" },
])" };
@@ -417,7 +396,7 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
- VERIFY_ARE_EQUAL(4u, appKeyBindings->_keyShortcuts.size());
+ VERIFY_ARE_EQUAL(3u, appKeyBindings->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast('C') };
@@ -439,15 +418,6 @@ namespace TerminalAppLocalTests
// Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order
VERIFY_ARE_EQUAL(static_cast(til::color(0x563412)), realArgs.TabColor().Value());
}
- {
- KeyChord kc{ true, false, false, static_cast('E') };
- auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
- VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
- const auto& realArgs = actionAndArgs.Args().try_as();
- VERIFY_IS_NOT_NULL(realArgs);
- // Verify the args have the expected value
- VERIFY_IS_NULL(realArgs.TabColor());
- }
{
KeyChord kc{ true, false, false, static_cast('F') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp
index 8aed7e4525d..f1eeddc2641 100644
--- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp
+++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp
@@ -76,6 +76,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(ValidateKeybindingsWarnings);
+ TEST_METHOD(ValidateExecuteCommandlineWarning);
+
TEST_METHOD(ValidateLegacyGlobalsWarning);
TEST_METHOD(TestTrailingCommas);
@@ -91,7 +93,7 @@ namespace TerminalAppLocalTests
void SettingsTests::TryCreateWinRTType()
{
- winrt::Microsoft::Terminal::Settings::TerminalSettings settings;
+ TerminalSettings settings;
VERIFY_IS_NOT_NULL(settings);
auto oldFontSize = settings.FontSize();
settings.FontSize(oldFontSize + 5);
@@ -1431,10 +1433,6 @@ namespace TerminalAppLocalTests
{
"name": "profile3",
"closeOnExit": null
- },
- {
- "name": "profile4",
- "closeOnExit": { "clearly": "not a string" }
}
]
})" };
@@ -1449,7 +1447,6 @@ namespace TerminalAppLocalTests
// Unknown modes parse as "Graceful"
VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings._profiles[3].GetCloseOnExitMode());
- VERIFY_ARE_EQUAL(CloseOnExitMode::Graceful, settings._profiles[4].GetCloseOnExitMode());
}
void SettingsTests::TestCloseOnExitCompatibilityShim()
{
@@ -2259,6 +2256,57 @@ namespace TerminalAppLocalTests
VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(3));
}
+ void SettingsTests::ValidateExecuteCommandlineWarning()
+ {
+ Log::Comment(L"This test is affected by GH#6949, so we're just skipping it for now.");
+ Log::Result(WEX::Logging::TestResults::Skipped);
+ return;
+
+ // const std::string badSettings{ R"(
+ // {
+ // "defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
+ // "profiles": [
+ // {
+ // "name" : "profile0",
+ // "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
+ // },
+ // {
+ // "name" : "profile1",
+ // "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}"
+ // }
+ // ],
+ // "keybindings": [
+ // { "name":null, "command": { "action": "wt" }, "keys": [ "ctrl+a" ] },
+ // { "name":null, "command": { "action": "wt", "commandline":"" }, "keys": [ "ctrl+b" ] },
+ // { "name":null, "command": { "action": "wt", "commandline":null }, "keys": [ "ctrl+c" ] }
+ // ]
+ // })" };
+
+ // const auto settingsObject = VerifyParseSucceeded(badSettings);
+
+ // auto settings = CascadiaSettings::FromJson(settingsObject);
+
+ // VERIFY_ARE_EQUAL(0u, settings->_globals._keybindings->_keyShortcuts.size());
+
+ // for (const auto& warning : settings->_globals._keybindingsWarnings)
+ // {
+ // Log::Comment(NoThrowString().Format(
+ // L"warning:%d", warning));
+ // }
+ // VERIFY_ARE_EQUAL(3u, settings->_globals._keybindingsWarnings.size());
+ // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_globals._keybindingsWarnings.at(0));
+ // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_globals._keybindingsWarnings.at(1));
+ // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_globals._keybindingsWarnings.at(2));
+
+ // settings->_ValidateKeybindings();
+
+ // VERIFY_ARE_EQUAL(4u, settings->_warnings.size());
+ // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.at(0));
+ // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(1));
+ // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(2));
+ // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(3));
+ }
+
void SettingsTests::ValidateLegacyGlobalsWarning()
{
const std::string badSettings{ R"(
diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp
index b515eacd93e..3c417174c59 100644
--- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp
+++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp
@@ -84,7 +84,7 @@ namespace TerminalAppLocalTests
{
// Verify we can create a WinRT type we authored
// Just creating it is enough to know that everything is working.
- winrt::Microsoft::Terminal::Settings::TerminalSettings settings;
+ TerminalSettings settings;
VERIFY_IS_NOT_NULL(settings);
auto oldFontSize = settings.FontSize();
settings.FontSize(oldFontSize + 5);
@@ -140,7 +140,7 @@ namespace TerminalAppLocalTests
// 4. one of our types that uses MUX/Xaml in this dll (Tab).
// Just creating all of them is enough to know that everything is working.
const auto profileGuid{ Utils::CreateGuid() };
- winrt::Microsoft::Terminal::Settings::TerminalSettings settings{};
+ TerminalSettings settings{};
VERIFY_IS_NOT_NULL(settings);
winrt::Microsoft::Terminal::TerminalConnection::EchoConnection conn{};
VERIFY_IS_NOT_NULL(conn);
diff --git a/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj b/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj
index ad2e4cf7675..c1745442044 100644
--- a/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj
+++ b/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj
@@ -114,7 +114,7 @@
- $(OpenConsoleDir)\packages\Taef.Redist.Wlk.10.51.200127004\lib\Microsoft.VisualStudio.TestPlatform.TestExecutor.WinRTCore.winmd
+ $(OpenConsoleDir)\packages\Taef.Redist.Wlk.10.57.200731005-develop\lib\Microsoft.VisualStudio.TestPlatform.TestExecutor.WinRTCore.winmd
true
diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp
index d5a151f3bf4..bb7112efb1b 100644
--- a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp
+++ b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp
@@ -21,7 +21,26 @@ static constexpr bool _IsMouseMessage(UINT uMsg)
return uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP || uMsg == WM_LBUTTONDBLCLK ||
uMsg == WM_MBUTTONDOWN || uMsg == WM_MBUTTONUP || uMsg == WM_MBUTTONDBLCLK ||
uMsg == WM_RBUTTONDOWN || uMsg == WM_RBUTTONUP || uMsg == WM_RBUTTONDBLCLK ||
- uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL;
+ uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL;
+}
+
+// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
+// See microsoft/terminal#2066 for more info.
+static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */) noexcept
+{
+ return false; // glyph is not wide.
+}
+
+static bool _EnsureStaticInitialization()
+{
+ // use C++11 magic statics to make sure we only do this once.
+ static bool initialized = []() {
+ // *** THIS IS A SINGLETON ***
+ SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback);
+
+ return true;
+ }();
+ return initialized;
}
LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc(
@@ -36,10 +55,29 @@ try
if (terminal)
{
- if (_IsMouseMessage(uMsg) && terminal->_CanSendVTMouseInput())
+ if (_IsMouseMessage(uMsg))
{
- if (terminal->_SendMouseEvent(uMsg, wParam, lParam))
+ if (terminal->_CanSendVTMouseInput() && terminal->_SendMouseEvent(uMsg, wParam, lParam))
{
+ // GH#6401: Capturing the mouse ensures that we get drag/release events
+ // even if the user moves outside the window.
+ // _SendMouseEvent returns false if the terminal's not in VT mode, so we'll
+ // fall through to release the capture.
+ switch (uMsg)
+ {
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ SetCapture(hwnd);
+ break;
+ case WM_LBUTTONUP:
+ case WM_MBUTTONUP:
+ case WM_RBUTTONUP:
+ ReleaseCapture();
+ break;
+ }
+
+ // Suppress all mouse events that made it into the terminal.
return 0;
}
}
@@ -57,6 +95,10 @@ try
return 0;
case WM_LBUTTONUP:
terminal->_singleClickTouchdownPos = std::nullopt;
+ [[fallthrough]];
+ case WM_MBUTTONUP:
+ case WM_RBUTTONUP:
+ ReleaseCapture();
break;
case WM_MOUSEMOVE:
if (WI_IsFlagSet(wParam, MK_LBUTTON))
@@ -72,7 +114,7 @@ try
{
const auto bufferData = terminal->_terminal->RetrieveSelectedTextFromBuffer(false);
LOG_IF_FAILED(terminal->_CopyTextToSystemClipboard(bufferData, true));
- terminal->_terminal->ClearSelection();
+ TerminalClearSelection(terminal);
}
CATCH_LOG();
}
@@ -81,6 +123,11 @@ try
terminal->_PasteTextFromClipboard();
}
return 0;
+ case WM_DESTROY:
+ // Release Terminal's hwnd so Teardown doesn't try to destroy it again
+ terminal->_hwnd.release();
+ terminal->Teardown();
+ return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
@@ -114,14 +161,16 @@ static bool RegisterTermClass(HINSTANCE hInstance) noexcept
}
HwndTerminal::HwndTerminal(HWND parentHwnd) :
- _desiredFont{ L"Consolas", 0, 10, { 0, 14 }, CP_UTF8 },
- _actualFont{ L"Consolas", 0, 10, { 0, 14 }, CP_UTF8, false },
+ _desiredFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8 },
+ _actualFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8, false },
_uiaProvider{ nullptr },
_uiaProviderInitialized{ false },
_currentDpi{ USER_DEFAULT_SCREEN_DPI },
_pfnWriteCallback{ nullptr },
_multiClickTime{ 500 } // this will be overwritten by the windows system double-click time
{
+ _EnsureStaticInitialization();
+
HINSTANCE hInstance = wil::GetModuleInstanceHandle();
if (RegisterTermClass(hInstance))
@@ -148,6 +197,11 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) :
}
}
+HwndTerminal::~HwndTerminal()
+{
+ Teardown();
+}
+
HRESULT HwndTerminal::Initialize()
{
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
@@ -162,9 +216,6 @@ HRESULT HwndTerminal::Initialize()
RETURN_IF_FAILED(dxEngine->Enable());
_renderer->AddRenderEngine(dxEngine.get());
- const auto pfn = std::bind(&::Microsoft::Console::Render::Renderer::IsGlyphWideByFont, _renderer.get(), std::placeholders::_1);
- SetGlyphWidthFallback(pfn);
-
_UpdateFont(USER_DEFAULT_SCREEN_DPI);
RECT windowRect;
GetWindowRect(_hwnd.get(), &windowRect);
@@ -181,8 +232,8 @@ HRESULT HwndTerminal::Initialize()
_terminal->SetBackgroundCallback([](auto) {});
_terminal->Create(COORD{ 80, 25 }, 1000, *_renderer);
- _terminal->SetDefaultBackground(RGB(5, 27, 80));
- _terminal->SetDefaultForeground(RGB(255, 255, 255));
+ _terminal->SetDefaultBackground(RGB(12, 12, 12));
+ _terminal->SetDefaultForeground(RGB(204, 204, 204));
_terminal->SetWriteInputCallback([=](std::wstring & input) noexcept { _WriteTextToConnection(input); });
localPointerToThread->EnablePainting();
@@ -191,6 +242,33 @@ HRESULT HwndTerminal::Initialize()
return S_OK;
}
+void HwndTerminal::Teardown() noexcept
+try
+{
+ // As a rule, detach resources from the Terminal before shutting them down.
+ // This ensures that teardown is reentrant.
+
+ // Shut down the renderer (and therefore the thread) before we implode
+ if (auto localRenderEngine{ std::exchange(_renderEngine, nullptr) })
+ {
+ if (auto localRenderer{ std::exchange(_renderer, nullptr) })
+ {
+ localRenderer->TriggerTeardown();
+ // renderer is destroyed
+ }
+ // renderEngine is destroyed
+ }
+
+ if (auto localHwnd{ _hwnd.release() })
+ {
+ // If we're being called through WM_DESTROY, we won't get here (hwnd is already released)
+ // If we're not, we may end up in Teardown _again_... but by the time we do, all other
+ // resources have been released and will not be released again.
+ DestroyWindow(localHwnd);
+ }
+}
+CATCH_LOG();
+
void HwndTerminal::RegisterScrollCallback(std::function callback)
{
_terminal->SetScrollPositionChangedCallback(callback);
@@ -467,11 +545,21 @@ try
}
CATCH_RETURN();
+void HwndTerminal::_ClearSelection() noexcept
+try
+{
+ auto lock{ _terminal->LockForWriting() };
+ _terminal->ClearSelection();
+ _renderer->TriggerSelection();
+}
+CATCH_LOG();
+
void _stdcall TerminalClearSelection(void* terminal)
{
- const auto publicTerminal = static_cast(terminal);
- publicTerminal->_terminal->ClearSelection();
+ auto publicTerminal = static_cast(terminal);
+ publicTerminal->_ClearSelection();
}
+
bool _stdcall TerminalIsSelectionActive(void* terminal)
{
const auto publicTerminal = static_cast(terminal);
@@ -482,9 +570,10 @@ bool _stdcall TerminalIsSelectionActive(void* terminal)
// Returns the selected text in the terminal.
const wchar_t* _stdcall TerminalGetSelection(void* terminal)
{
- const auto publicTerminal = static_cast(terminal);
+ auto publicTerminal = static_cast(terminal);
const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false);
+ publicTerminal->_ClearSelection();
// convert text: vector --> string
std::wstring selectedText;
@@ -494,8 +583,6 @@ const wchar_t* _stdcall TerminalGetSelection(void* terminal)
}
auto returnText = wil::make_cotaskmem_string_nothrow(selectedText.c_str());
- TerminalClearSelection(terminal);
-
return returnText.release();
}
@@ -541,16 +628,21 @@ bool HwndTerminal::_CanSendVTMouseInput() const noexcept
bool HwndTerminal::_SendMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept
try
{
- const til::point cursorPosition{
+ til::point cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
};
const til::size fontSize{ this->_actualFont.GetSize() };
short wheelDelta{ 0 };
- if (uMsg == WM_MOUSEWHEEL)
+ if (uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL)
{
wheelDelta = HIWORD(wParam);
+
+ // If it's a *WHEEL event, it's in screen coordinates, not window (?!)
+ POINT coordsToTransform = cursorPosition;
+ ScreenToClient(_hwnd.get(), &coordsToTransform);
+ cursorPosition = coordsToTransform;
}
return _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta);
@@ -561,20 +653,24 @@ catch (...)
return false;
}
-void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode, bool keyDown) noexcept
+void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode, WORD flags, bool keyDown) noexcept
try
{
- const auto flags = getControlKeyState();
- _terminal->SendKeyEvent(vkey, scanCode, flags, keyDown);
+ auto modifiers = getControlKeyState();
+ if (WI_IsFlagSet(flags, ENHANCED_KEY))
+ {
+ modifiers |= ControlKeyStates::EnhancedKey;
+ }
+ _terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown);
}
CATCH_LOG();
-void HwndTerminal::_SendCharEvent(wchar_t ch, WORD scanCode) noexcept
+void HwndTerminal::_SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept
try
{
if (_terminal->IsSelectionActive())
{
- _terminal->ClearSelection();
+ _ClearSelection();
if (ch == UNICODE_ESC)
{
// ESC should clear any selection before it triggers input.
@@ -589,21 +685,25 @@ try
return;
}
- const auto flags = getControlKeyState();
- _terminal->SendCharEvent(ch, scanCode, flags);
+ auto modifiers = getControlKeyState();
+ if (WI_IsFlagSet(flags, ENHANCED_KEY))
+ {
+ modifiers |= ControlKeyStates::EnhancedKey;
+ }
+ _terminal->SendCharEvent(ch, scanCode, modifiers);
}
CATCH_LOG();
-void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown)
+void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown)
{
const auto publicTerminal = static_cast(terminal);
- publicTerminal->_SendKeyEvent(vkey, scanCode, keyDown);
+ publicTerminal->_SendKeyEvent(vkey, scanCode, flags, keyDown);
}
-void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode)
+void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode, WORD flags)
{
const auto publicTerminal = static_cast(terminal);
- publicTerminal->_SendCharEvent(ch, scanCode);
+ publicTerminal->_SendCharEvent(ch, scanCode, flags);
}
void _stdcall DestroyTerminal(void* terminal)
@@ -632,7 +732,7 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
publicTerminal->_terminal->SetCursorStyle(theme.CursorStyle);
- publicTerminal->_desiredFont = { fontFamily, 0, 10, { 0, fontSize }, CP_UTF8 };
+ publicTerminal->_desiredFont = { fontFamily, 0, DEFAULT_FONT_WEIGHT, { 0, fontSize }, CP_UTF8 };
publicTerminal->_UpdateFont(newDpi);
// When the font changes the terminal dimensions need to be recalculated since the available row and column
diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.hpp b/src/cascadia/PublicTerminalCore/HwndTerminal.hpp
index 90b4cc50ac8..47ee1f88dc7 100644
--- a/src/cascadia/PublicTerminalCore/HwndTerminal.hpp
+++ b/src/cascadia/PublicTerminalCore/HwndTerminal.hpp
@@ -34,8 +34,8 @@ __declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal);
__declspec(dllexport) void _stdcall DestroyTerminal(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
__declspec(dllexport) void _stdcall TerminalRegisterWriteCallback(void* terminal, const void __stdcall callback(wchar_t*));
-__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown);
-__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
+__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown);
+__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD flags, WORD scanCode);
__declspec(dllexport) void _stdcall TerminalBlinkCursor(void* terminal);
__declspec(dllexport) void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
__declspec(dllexport) void _stdcall TerminalSetFocus(void* terminal);
@@ -51,9 +51,10 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
HwndTerminal(HwndTerminal&&) = default;
HwndTerminal& operator=(const HwndTerminal&) = default;
HwndTerminal& operator=(HwndTerminal&&) = default;
- ~HwndTerminal() = default;
+ ~HwndTerminal();
HRESULT Initialize();
+ void Teardown() noexcept;
void SendOutput(std::wstring_view data);
HRESULT Refresh(const SIZE windowSize, _Out_ COORD* dimensions);
void RegisterScrollCallback(std::function callback);
@@ -92,8 +93,8 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
friend void _stdcall TerminalClearSelection(void* terminal);
friend const wchar_t* _stdcall TerminalGetSelection(void* terminal);
friend bool _stdcall TerminalIsSelectionActive(void* terminal);
- friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown);
- friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
+ friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown);
+ friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode, WORD flags);
friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
friend void _stdcall TerminalBlinkCursor(void* terminal);
friend void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
@@ -112,11 +113,13 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
HRESULT _MoveSelection(LPARAM lParam) noexcept;
IRawElementProviderSimple* _GetUiaProvider() noexcept;
+ void _ClearSelection() noexcept;
+
bool _CanSendVTMouseInput() const noexcept;
bool _SendMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept;
- void _SendKeyEvent(WORD vkey, WORD scanCode, bool keyDown) noexcept;
- void _SendCharEvent(wchar_t ch, WORD scanCode) noexcept;
+ void _SendKeyEvent(WORD vkey, WORD scanCode, WORD flags, bool keyDown) noexcept;
+ void _SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept;
// Inherited via IControlAccessibilityInfo
COORD GetFontSize() const override;
diff --git a/src/cascadia/TerminalApp/ActionAndArgs.cpp b/src/cascadia/TerminalApp/ActionAndArgs.cpp
index 5375ea02cb0..f28bfcca8a6 100644
--- a/src/cascadia/TerminalApp/ActionAndArgs.cpp
+++ b/src/cascadia/TerminalApp/ActionAndArgs.cpp
@@ -2,6 +2,9 @@
#include "ActionArgs.h"
#include "ActionAndArgs.h"
#include "ActionAndArgs.g.cpp"
+
+#include "JsonUtils.h"
+
#include
static constexpr std::string_view CopyTextKey{ "copy" };
@@ -35,6 +38,7 @@ static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" };
static constexpr std::string_view SetTabColorKey{ "setTabColor" };
static constexpr std::string_view OpenTabColorPickerKey{ "openTabColorPicker" };
static constexpr std::string_view RenameTabKey{ "renameTab" };
+static constexpr std::string_view ExecuteCommandlineKey{ "wt" };
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
static constexpr std::string_view ActionKey{ "action" };
@@ -44,6 +48,8 @@ static constexpr std::string_view UnboundKey{ "unbound" };
namespace winrt::TerminalApp::implementation
{
+ using namespace ::TerminalApp;
+
// Specifically use a map here over an unordered_map. We want to be able to
// iterate over these entries in-order when we're serializing the keybindings.
// HERE BE DRAGONS:
@@ -84,6 +90,7 @@ namespace winrt::TerminalApp::implementation
{ UnboundKey, ShortcutAction::Invalid },
{ FindKey, ShortcutAction::Find },
{ RenameTabKey, ShortcutAction::RenameTab },
+ { ExecuteCommandlineKey, ShortcutAction::ExecuteCommandline },
{ ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette },
};
@@ -116,6 +123,8 @@ namespace winrt::TerminalApp::implementation
{ ShortcutAction::RenameTab, winrt::TerminalApp::implementation::RenameTabArgs::FromJson },
+ { ShortcutAction::ExecuteCommandline, winrt::TerminalApp::implementation::ExecuteCommandlineArgs::FromJson },
+
{ ShortcutAction::Invalid, nullptr },
};
@@ -183,11 +192,9 @@ namespace winrt::TerminalApp::implementation
}
else if (json.isObject())
{
- const auto actionVal = json[JsonKey(ActionKey)];
- if (actionVal.isString())
+ if (const auto actionString{ JsonUtils::GetValueForKey>(json, ActionKey) })
{
- auto actionString = actionVal.asString();
- action = GetActionFromString(actionString);
+ action = GetActionFromString(*actionString);
argsVal = json;
}
}
@@ -265,6 +272,7 @@ namespace winrt::TerminalApp::implementation
{ ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") },
{ ShortcutAction::OpenTabColorPicker, RS_(L"OpenTabColorPickerCommandKey") },
{ ShortcutAction::RenameTab, RS_(L"ResetTabNameCommandKey") },
+ { ShortcutAction::ExecuteCommandline, RS_(L"ExecuteCommandlineCommandKey") },
{ ShortcutAction::ToggleCommandPalette, RS_(L"ToggleCommandPaletteCommandKey") },
};
}();
@@ -281,5 +289,4 @@ namespace winrt::TerminalApp::implementation
const auto found = GeneratedActionNames.find(_Action);
return found != GeneratedActionNames.end() ? found->second : L"";
}
-
}
diff --git a/src/cascadia/TerminalApp/ActionArgs.cpp b/src/cascadia/TerminalApp/ActionArgs.cpp
index 012eacd7f22..a5a83203afa 100644
--- a/src/cascadia/TerminalApp/ActionArgs.cpp
+++ b/src/cascadia/TerminalApp/ActionArgs.cpp
@@ -17,6 +17,7 @@
#include "OpenSettingsArgs.g.cpp"
#include "SetTabColorArgs.g.cpp"
#include "RenameTabArgs.g.cpp"
+#include "ExecuteCommandlineArgs.g.cpp"
#include
@@ -258,4 +259,17 @@ namespace winrt::TerminalApp::implementation
return RS_(L"ResetTabNameCommandKey");
}
+ winrt::hstring ExecuteCommandlineArgs::GenerateName() const
+ {
+ // "Run commandline "{_Commandline}" in this window"
+ if (!_Commandline.empty())
+ {
+ return winrt::hstring{
+ fmt::format(std::wstring_view(RS_(L"ExecuteCommandlineCommandKey")),
+ _Commandline.c_str())
+ };
+ }
+ return L"";
+ }
+
}
diff --git a/src/cascadia/TerminalApp/ActionArgs.h b/src/cascadia/TerminalApp/ActionArgs.h
index eb233b5967a..091774230d1 100644
--- a/src/cascadia/TerminalApp/ActionArgs.h
+++ b/src/cascadia/TerminalApp/ActionArgs.h
@@ -17,12 +17,15 @@
#include "OpenSettingsArgs.g.h"
#include "SetTabColorArgs.g.h"
#include "RenameTabArgs.g.h"
+#include "ExecuteCommandlineArgs.g.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
#include "Utils.h"
#include "JsonUtils.h"
#include "TerminalWarnings.h"
+#include "TerminalSettingsSerializationHelpers.h"
+
// Notes on defining ActionArgs and ActionEventArgs:
// * All properties specific to an action should be defined as an ActionArgs
// class that implements IActionArgs
@@ -31,6 +34,7 @@
namespace winrt::TerminalApp::implementation
{
+ using namespace ::TerminalApp;
using FromJsonResult = std::tuple>;
struct ActionEventArgs : public ActionEventArgsT
@@ -73,26 +77,11 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self();
- if (auto commandline{ json[JsonKey(CommandlineKey)] })
- {
- args->_Commandline = winrt::to_hstring(commandline.asString());
- }
- if (auto startingDirectory{ json[JsonKey(StartingDirectoryKey)] })
- {
- args->_StartingDirectory = winrt::to_hstring(startingDirectory.asString());
- }
- if (auto tabTitle{ json[JsonKey(TabTitleKey)] })
- {
- args->_TabTitle = winrt::to_hstring(tabTitle.asString());
- }
- if (auto index{ json[JsonKey(ProfileIndexKey)] })
- {
- args->_ProfileIndex = index.asInt();
- }
- if (auto profile{ json[JsonKey(ProfileKey)] })
- {
- args->_Profile = winrt::to_hstring(profile.asString());
- }
+ JsonUtils::GetValueForKey(json, CommandlineKey, args->_Commandline);
+ JsonUtils::GetValueForKey(json, StartingDirectoryKey, args->_StartingDirectory);
+ JsonUtils::GetValueForKey(json, TabTitleKey, args->_TabTitle);
+ JsonUtils::GetValueForKey(json, ProfileIndexKey, args->_ProfileIndex);
+ JsonUtils::GetValueForKey(json, ProfileKey, args->_Profile);
return *args;
}
};
@@ -120,10 +109,7 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self();
- if (auto singleLine{ json[JsonKey(SingleLineKey)] })
- {
- args->_SingleLine = singleLine.asBool();
- }
+ JsonUtils::GetValueForKey(json, SingleLineKey, args->_SingleLine);
return { *args, {} };
}
};
@@ -177,49 +163,11 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self();
- if (auto tabIndex{ json[JsonKey(TabIndexKey)] })
- {
- args->_TabIndex = tabIndex.asUInt();
- }
+ JsonUtils::GetValueForKey(json, TabIndexKey, args->_TabIndex);
return { *args, {} };
}
};
- // Possible Direction values
- // TODO:GH#2550/#3475 - move these to a centralized deserializing place
- static constexpr std::string_view LeftString{ "left" };
- static constexpr std::string_view RightString{ "right" };
- static constexpr std::string_view UpString{ "up" };
- static constexpr std::string_view DownString{ "down" };
-
- // Function Description:
- // - Helper function for parsing a Direction from a string
- // Arguments:
- // - directionString: the string to attempt to parse
- // Return Value:
- // - The encoded Direction value, or Direction::None if it was an invalid string
- static TerminalApp::Direction ParseDirection(const std::string& directionString)
- {
- if (directionString == LeftString)
- {
- return TerminalApp::Direction::Left;
- }
- else if (directionString == RightString)
- {
- return TerminalApp::Direction::Right;
- }
- else if (directionString == UpString)
- {
- return TerminalApp::Direction::Up;
- }
- else if (directionString == DownString)
- {
- return TerminalApp::Direction::Down;
- }
- // default behavior for invalid data
- return TerminalApp::Direction::None;
- };
-
struct ResizePaneArgs : public ResizePaneArgsT
{
ResizePaneArgs() = default;
@@ -243,10 +191,7 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self();
- if (auto directionString{ json[JsonKey(DirectionKey)] })
- {
- args->_Direction = ParseDirection(directionString.asString());
- }
+ JsonUtils::GetValueForKey(json, DirectionKey, args->_Direction);
if (args->_Direction == TerminalApp::Direction::None)
{
return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
@@ -281,10 +226,7 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self();
- if (auto directionString{ json[JsonKey(DirectionKey)] })
- {
- args->_Direction = ParseDirection(directionString.asString());
- }
+ JsonUtils::GetValueForKey(json, DirectionKey, args->_Direction);
if (args->_Direction == TerminalApp::Direction::None)
{
return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
@@ -319,48 +261,11 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self();
- if (auto jsonDelta{ json[JsonKey(AdjustFontSizeDelta)] })
- {
- args->_Delta = jsonDelta.asInt();
- }
+ JsonUtils::GetValueForKey(json, AdjustFontSizeDelta, args->_Delta);
return { *args, {} };
}
};
- // Possible SplitState values
- // TODO:GH#2550/#3475 - move these to a centralized deserializing place
- static constexpr std::string_view VerticalKey{ "vertical" };
- static constexpr std::string_view HorizontalKey{ "horizontal" };
- static constexpr std::string_view AutomaticKey{ "auto" };
- static TerminalApp::SplitState ParseSplitState(const std::string& stateString)
- {
- if (stateString == VerticalKey)
- {
- return TerminalApp::SplitState::Vertical;
- }
- else if (stateString == HorizontalKey)
- {
- return TerminalApp::SplitState::Horizontal;
- }
- else if (stateString == AutomaticKey)
- {
- return TerminalApp::SplitState::Automatic;
- }
- // default behavior for invalid data
- return TerminalApp::SplitState::Automatic;
- };
-
- // Possible SplitType values
- static constexpr std::string_view DuplicateKey{ "duplicate" };
- static TerminalApp::SplitType ParseSplitModeState(const std::string& stateString)
- {
- if (stateString == DuplicateKey)
- {
- return TerminalApp::SplitType::Duplicate;
- }
- return TerminalApp::SplitType::Manual;
- }
-
struct SplitPaneArgs : public SplitPaneArgsT
{
SplitPaneArgs() = default;
@@ -391,48 +296,12 @@ namespace winrt::TerminalApp::implementation
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self();
args->_TerminalArgs = NewTerminalArgs::FromJson(json);
- if (auto jsonStyle{ json[JsonKey(SplitKey)] })
- {
- args->_SplitStyle = ParseSplitState(jsonStyle.asString());
- }
- if (auto jsonStyle{ json[JsonKey(SplitModeKey)] })
- {
- args->_SplitMode = ParseSplitModeState(jsonStyle.asString());
- }
+ JsonUtils::GetValueForKey(json, SplitKey, args->_SplitStyle);
+ JsonUtils::GetValueForKey(json, SplitModeKey, args->_SplitMode);
return { *args, {} };
}
};
- // Possible SettingsTarget values
- // TODO:GH#2550/#3475 - move these to a centralized deserializing place
- static constexpr std::string_view SettingsFileString{ "settingsFile" };
- static constexpr std::string_view DefaultsFileString{ "defaultsFile" };
- static constexpr std::string_view AllFilesString{ "allFiles" };
-
- // Function Description:
- // - Helper function for parsing a SettingsTarget from a string
- // Arguments:
- // - targetString: the string to attempt to parse
- // Return Value:
- // - The encoded SettingsTarget value, or SettingsTarget::SettingsFile if it was an invalid string
- static TerminalApp::SettingsTarget ParseSettingsTarget(const std::string& targetString)
- {
- if (targetString == SettingsFileString)
- {
- return TerminalApp::SettingsTarget::SettingsFile;
- }
- else if (targetString == DefaultsFileString)
- {
- return TerminalApp::SettingsTarget::DefaultsFile;
- }
- else if (targetString == AllFilesString)
- {
- return TerminalApp::SettingsTarget::AllFiles;
- }
- // default behavior for invalid data
- return TerminalApp::SettingsTarget::SettingsFile;
- };
-
struct OpenSettingsArgs : public OpenSettingsArgsT
{
OpenSettingsArgs() = default;
@@ -456,10 +325,7 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self();
- if (auto targetString{ json[JsonKey(TargetKey)] })
- {
- args->_Target = ParseSettingsTarget(targetString.asString());
- }
+ JsonUtils::GetValueForKey(json, TargetKey, args->_Target);
return { *args, {} };
}
};
@@ -487,16 +353,10 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self();
- std::optional temp;
- try
+ if (const auto temp{ JsonUtils::GetValueForKey>(json, ColorKey) })
{
- ::TerminalApp::JsonUtils::GetOptionalColor(json, ColorKey, temp);
- if (temp.has_value())
- {
- args->_TabColor = static_cast(temp.value());
- }
+ args->_TabColor = static_cast(*temp);
}
- CATCH_LOG();
return { *args, {} };
}
};
@@ -524,13 +384,43 @@ namespace winrt::TerminalApp::implementation
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self();
- if (auto title{ json[JsonKey(TitleKey)] })
+ JsonUtils::GetValueForKey(json, TitleKey, args->_Title);
+ return { *args, {} };
+ }
+ };
+
+ struct ExecuteCommandlineArgs : public ExecuteCommandlineArgsT
+ {
+ ExecuteCommandlineArgs() = default;
+ GETSET_PROPERTY(winrt::hstring, Commandline, L"");
+
+ static constexpr std::string_view CommandlineKey{ "commandline" };
+
+ public:
+ hstring GenerateName() const;
+
+ bool Equals(const IActionArgs& other)
+ {
+ auto otherAsUs = other.try_as();
+ if (otherAsUs)
+ {
+ return otherAsUs->_Commandline == _Commandline;
+ }
+ return false;
+ };
+ static FromJsonResult FromJson(const Json::Value& json)
+ {
+ // LOAD BEARING: Not using make_self here _will_ break you in the future!
+ auto args = winrt::make_self();
+ JsonUtils::GetValueForKey(json, CommandlineKey, args->_Commandline);
+ if (args->_Commandline.empty())
{
- args->_Title = winrt::to_hstring(title.asString());
+ return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } };
}
return { *args, {} };
}
};
+
}
namespace winrt::TerminalApp::factory_implementation
diff --git a/src/cascadia/TerminalApp/ActionArgs.idl b/src/cascadia/TerminalApp/ActionArgs.idl
index 36d5025bb4a..84e5588bf2d 100644
--- a/src/cascadia/TerminalApp/ActionArgs.idl
+++ b/src/cascadia/TerminalApp/ActionArgs.idl
@@ -115,4 +115,9 @@ namespace TerminalApp
{
String Title { get; };
};
+
+ [default_interface] runtimeclass ExecuteCommandlineArgs : IActionArgs
+ {
+ String Commandline;
+ };
}
diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp
index 271513d4e58..63a2743d80b 100644
--- a/src/cascadia/TerminalApp/AppActionHandlers.cpp
+++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp
@@ -11,6 +11,7 @@ using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Text;
using namespace winrt::Windows::UI::Core;
+using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::System;
using namespace winrt::Microsoft::Terminal;
using namespace winrt::Microsoft::Terminal::Settings;
@@ -334,4 +335,20 @@ namespace winrt::TerminalApp::implementation
}
args.Handled(true);
}
+
+ void TerminalPage::_HandleExecuteCommandline(const IInspectable& /*sender*/,
+ const TerminalApp::ActionEventArgs& actionArgs)
+ {
+ if (const auto& realArgs = actionArgs.ActionArgs().try_as())
+ {
+ auto actions = winrt::single_threaded_vector(std::move(
+ TerminalPage::ConvertExecuteCommandlineToActions(realArgs)));
+
+ if (_startupActions.Size() != 0)
+ {
+ actionArgs.Handled(true);
+ _ProcessStartupActions(actions, false);
+ }
+ }
+ }
}
diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp
index 753afb2fe6e..70e6df6940b 100644
--- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp
+++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp
@@ -599,7 +599,7 @@ void AppCommandlineArgs::_addCommandsForArg(std::vector& commands,
// -
// Return Value:
// - the deque of actions we've buffered as a result of parsing commands.
-std::deque& AppCommandlineArgs::GetStartupActions()
+std::vector& AppCommandlineArgs::GetStartupActions()
{
return _startupActions;
}
@@ -658,7 +658,8 @@ void AppCommandlineArgs::ValidateStartupCommands()
auto newTerminalArgs = winrt::make_self();
args->TerminalArgs(*newTerminalArgs);
newTabAction->Args(*args);
- _startupActions.push_front(*newTabAction);
+ // push the arg onto the front
+ _startupActions.insert(_startupActions.begin(), 1, *newTabAction);
}
}
@@ -666,3 +667,52 @@ std::optional AppCommandlineArgs::GetLaunchMode(
{
return _launchMode;
}
+
+// Method Description:
+// - Attempts to parse an array of commandline args into a list of
+// commands to execute, and then parses these commands. As commands are
+// successfully parsed, they will generate ShortcutActions for us to be
+// able to execute. If we fail to parse any commands, we'll return the
+// error code from the failure to parse that command, and stop processing
+// additional commands.
+// - The first arg in args should be the program name "wt" (or some variant). It
+// will be ignored during parsing.
+// Arguments:
+// - args: an array of strings to process as a commandline. These args can contain spaces
+// Return Value:
+// - 0 if the commandline was successfully parsed
+int AppCommandlineArgs::ParseArgs(winrt::array_view& args)
+{
+ auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args);
+
+ for (auto& cmdBlob : commands)
+ {
+ // On one hand, it seems like we should be able to have one
+ // AppCommandlineArgs for parsing all of them, and collect the
+ // results one at a time.
+ //
+ // On the other hand, re-using a CLI::App seems to leave state from
+ // previous parsings around, so we could get mysterious behavior
+ // where one command affects the values of the next.
+ //
+ // From https://cliutils.github.io/CLI11/book/chapters/options.html:
+ // > If that option is not given, CLI11 will not touch the initial
+ // > value. This allows you to set up defaults by simply setting
+ // > your value beforehand.
+ //
+ // So we pretty much need the to either manually reset the state
+ // each command, or build new ones.
+ const auto result = ParseCommand(cmdBlob);
+
+ // If this succeeded, result will be 0. Otherwise, the caller should
+ // exit(result), to exit the program.
+ if (result != 0)
+ {
+ return result;
+ }
+ }
+
+ // If all the args were successfully parsed, we'll have some commands
+ // built in _appArgs, which we'll use when the application starts up.
+ return 0;
+}
diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.h b/src/cascadia/TerminalApp/AppCommandlineArgs.h
index 00c7f3cd904..a0d87ffc6b8 100644
--- a/src/cascadia/TerminalApp/AppCommandlineArgs.h
+++ b/src/cascadia/TerminalApp/AppCommandlineArgs.h
@@ -28,13 +28,15 @@ class TerminalApp::AppCommandlineArgs final
AppCommandlineArgs();
~AppCommandlineArgs() = default;
+
int ParseCommand(const Commandline& command);
+ int ParseArgs(winrt::array_view& args);
static std::vector BuildCommands(const std::vector& args);
static std::vector BuildCommands(winrt::array_view& args);
void ValidateStartupCommands();
- std::deque& GetStartupActions();
+ std::vector& GetStartupActions();
const std::string& GetExitMessage();
bool ShouldExitEarly() const noexcept;
@@ -90,7 +92,7 @@ class TerminalApp::AppCommandlineArgs final
std::optional _launchMode{ std::nullopt };
// Are you adding more args here? Make sure to reset them in _resetStateToDefault
- std::deque _startupActions;
+ std::vector _startupActions;
std::string _exitMessage;
bool _shouldExitEarly{ false };
diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp
index e036bd9ee2c..5a6ec188703 100644
--- a/src/cascadia/TerminalApp/AppLogic.cpp
+++ b/src/cascadia/TerminalApp/AppLogic.cpp
@@ -474,7 +474,7 @@ namespace winrt::TerminalApp::implementation
// -
// Return Value:
// - a point containing the requested dimensions in pixels.
- winrt::Windows::Foundation::Point AppLogic::GetLaunchDimensions(uint32_t dpi)
+ winrt::Windows::Foundation::Size AppLogic::GetLaunchDimensions(uint32_t dpi)
{
if (!_loadedInitialSettings)
{
@@ -504,7 +504,7 @@ namespace winrt::TerminalApp::implementation
// of the height calculation here.
auto titlebar = TitlebarControl{ static_cast(0) };
titlebar.Measure({ SHRT_MAX, SHRT_MAX });
- proposedSize.Y += (titlebar.DesiredSize().Height) * scale;
+ proposedSize.Height += (titlebar.DesiredSize().Height) * scale;
}
else if (_settings->GlobalSettings().AlwaysShowTabs())
{
@@ -519,7 +519,7 @@ namespace winrt::TerminalApp::implementation
// For whatever reason, there's about 6px of unaccounted-for space
// in the application. I couldn't tell you where these 6px are
// coming from, but they need to be included in this math.
- proposedSize.Y += (tabControl.DesiredSize().Height + 6) * scale;
+ proposedSize.Width += (tabControl.DesiredSize().Height + 6) * scale;
}
return proposedSize;
@@ -974,7 +974,7 @@ namespace winrt::TerminalApp::implementation
// or 0. (see AppLogic::_ParseArgs)
int32_t AppLogic::SetStartupCommandline(array_view args)
{
- const auto result = _ParseArgs(args);
+ const auto result = _appArgs.ParseArgs(args);
if (result == 0)
{
_appArgs.ValidateStartupCommands();
@@ -984,53 +984,6 @@ namespace winrt::TerminalApp::implementation
return result;
}
- // Method Description:
- // - Attempts to parse an array of commandline args into a list of
- // commands to execute, and then parses these commands. As commands are
- // successfully parsed, they will generate ShortcutActions for us to be
- // able to execute. If we fail to parse any commands, we'll return the
- // error code from the failure to parse that command, and stop processing
- // additional commands.
- // Arguments:
- // - args: an array of strings to process as a commandline. These args can contain spaces
- // Return Value:
- // - 0 if the commandline was successfully parsed
- int AppLogic::_ParseArgs(winrt::array_view& args)
- {
- auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args);
-
- for (auto& cmdBlob : commands)
- {
- // On one hand, it seems like we should be able to have one
- // AppCommandlineArgs for parsing all of them, and collect the
- // results one at a time.
- //
- // On the other hand, re-using a CLI::App seems to leave state from
- // previous parsings around, so we could get mysterious behavior
- // where one command affects the values of the next.
- //
- // From https://cliutils.github.io/CLI11/book/chapters/options.html:
- // > If that option is not given, CLI11 will not touch the initial
- // > value. This allows you to set up defaults by simply setting
- // > your value beforehand.
- //
- // So we pretty much need the to either manually reset the state
- // each command, or build new ones.
- const auto result = _appArgs.ParseCommand(cmdBlob);
-
- // If this succeeded, result will be 0. Otherwise, the caller should
- // exit(result), to exit the program.
- if (result != 0)
- {
- return result;
- }
- }
-
- // If all the args were successfully parsed, we'll have some commands
- // built in _appArgs, which we'll use when the application starts up.
- return 0;
- }
-
// Method Description:
// - If there were any errors parsing the commandline that was used to
// initialize the terminal, this will return a string containing that
diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h
index c879a6f1876..de2b225f81f 100644
--- a/src/cascadia/TerminalApp/AppLogic.h
+++ b/src/cascadia/TerminalApp/AppLogic.h
@@ -37,7 +37,7 @@ namespace winrt::TerminalApp::implementation
bool Fullscreen() const;
bool AlwaysOnTop() const;
- Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
+ Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi);
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();
LaunchMode GetLaunchMode();
diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl
index 071a5ce84cc..00006608ef1 100644
--- a/src/cascadia/TerminalApp/AppLogic.idl
+++ b/src/cascadia/TerminalApp/AppLogic.idl
@@ -45,7 +45,8 @@ namespace TerminalApp
Boolean Fullscreen { get; };
Boolean AlwaysOnTop { get; };
- Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi);
+ Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi);
+
Windows.Foundation.Point GetLaunchInitialPositions(Int32 defaultInitialX, Int32 defaultInitialY);
Windows.UI.Xaml.ElementTheme GetRequestedTheme();
LaunchMode GetLaunchMode();
diff --git a/src/cascadia/TerminalApp/CascadiaSettings.h b/src/cascadia/TerminalApp/CascadiaSettings.h
index 004f3809423..08a65fba5d7 100644
--- a/src/cascadia/TerminalApp/CascadiaSettings.h
+++ b/src/cascadia/TerminalApp/CascadiaSettings.h
@@ -54,8 +54,8 @@ class TerminalApp::CascadiaSettings final
static const CascadiaSettings& GetCurrentAppSettings();
- std::tuple BuildSettings(const winrt::TerminalApp::NewTerminalArgs& newTerminalArgs) const;
- winrt::Microsoft::Terminal::Settings::TerminalSettings BuildSettings(GUID profileGuid) const;
+ std::tuple BuildSettings(const winrt::TerminalApp::NewTerminalArgs& newTerminalArgs) const;
+ winrt::TerminalApp::TerminalSettings BuildSettings(GUID profileGuid) const;
GlobalAppSettings& GlobalSettings();
diff --git a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp
index 2ed23f8c770..1a8f2b6e71f 100644
--- a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp
+++ b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp
@@ -249,9 +249,9 @@ void CascadiaSettings::_LoadDynamicProfiles()
const auto disabledProfileSources = CascadiaSettings::_GetDisabledProfileSourcesJsonObject(_userSettings);
if (disabledProfileSources.isArray())
{
- for (const auto& ns : disabledProfileSources)
+ for (const auto& json : disabledProfileSources)
{
- ignoredNamespaces.emplace(GetWstringFromJson(ns));
+ ignoredNamespaces.emplace(JsonUtils::GetValue(json));
}
}
diff --git a/src/cascadia/TerminalApp/ColorScheme.cpp b/src/cascadia/TerminalApp/ColorScheme.cpp
index 45d4e7748e4..1659013809c 100644
--- a/src/cascadia/TerminalApp/ColorScheme.cpp
+++ b/src/cascadia/TerminalApp/ColorScheme.cpp
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
#include "pch.h"
+#include
#include "ColorScheme.h"
#include "DefaultSettings.h"
#include "../../types/inc/Utils.hpp"
@@ -10,8 +11,7 @@
using namespace ::Microsoft::Console;
using namespace TerminalApp;
-using namespace winrt::Microsoft::Terminal::Settings;
-using namespace winrt::Microsoft::Terminal::TerminalControl;
+using namespace winrt::TerminalApp;
static constexpr std::string_view NameKey{ "name" };
static constexpr std::string_view ForegroundKey{ "foreground" };
@@ -105,9 +105,9 @@ ColorScheme ColorScheme::FromJson(const Json::Value& json)
// - true iff the json object has the same `name` as we do.
bool ColorScheme::ShouldBeLayered(const Json::Value& json) const
{
- if (const auto name{ json[JsonKey(NameKey)] })
+ std::wstring nameFromJson{};
+ if (JsonUtils::GetValueForKey(json, NameKey, nameFromJson))
{
- const auto nameFromJson = GetWstringFromJson(name);
return nameFromJson == _schemeName;
}
return false;
@@ -125,39 +125,16 @@ bool ColorScheme::ShouldBeLayered(const Json::Value& json) const
//
void ColorScheme::LayerJson(const Json::Value& json)
{
- if (auto name{ json[JsonKey(NameKey)] })
- {
- _schemeName = winrt::to_hstring(name.asString());
- }
- if (auto fgString{ json[JsonKey(ForegroundKey)] })
- {
- const auto color = Utils::ColorFromHexString(fgString.asString());
- _defaultForeground = color;
- }
- if (auto bgString{ json[JsonKey(BackgroundKey)] })
- {
- const auto color = Utils::ColorFromHexString(bgString.asString());
- _defaultBackground = color;
- }
- if (auto sbString{ json[JsonKey(SelectionBackgroundKey)] })
- {
- const auto color = Utils::ColorFromHexString(sbString.asString());
- _selectionBackground = color;
- }
- if (auto sbString{ json[JsonKey(CursorColorKey)] })
- {
- const auto color = Utils::ColorFromHexString(sbString.asString());
- _cursorColor = color;
- }
+ JsonUtils::GetValueForKey(json, NameKey, _schemeName);
+ JsonUtils::GetValueForKey(json, ForegroundKey, _defaultForeground);
+ JsonUtils::GetValueForKey(json, BackgroundKey, _defaultBackground);
+ JsonUtils::GetValueForKey(json, SelectionBackgroundKey, _selectionBackground);
+ JsonUtils::GetValueForKey(json, CursorColorKey, _cursorColor);
int i = 0;
for (const auto& current : TableColors)
{
- if (auto str{ json[JsonKey(current)] })
- {
- const auto color = Utils::ColorFromHexString(str.asString());
- _table.at(i) = color;
- }
+ JsonUtils::GetValueForKey(json, current, _table.at(i));
i++;
}
}
@@ -200,11 +177,7 @@ til::color ColorScheme::GetCursorColor() const noexcept
// - the name of the color scheme represented by `json` as a std::wstring optional
// i.e. the value of the `name` property.
// - returns std::nullopt if `json` doesn't have the `name` property
-std::optional TerminalApp::ColorScheme::GetNameFromJson(const Json::Value& json)
+std::optional ColorScheme::GetNameFromJson(const Json::Value& json)
{
- if (const auto name{ json[JsonKey(NameKey)] })
- {
- return GetWstringFromJson(name);
- }
- return std::nullopt;
+ return JsonUtils::GetValueForKey>(json, NameKey);
}
diff --git a/src/cascadia/TerminalApp/ColorScheme.h b/src/cascadia/TerminalApp/ColorScheme.h
index 92f8f4f6d70..dca0f53759a 100644
--- a/src/cascadia/TerminalApp/ColorScheme.h
+++ b/src/cascadia/TerminalApp/ColorScheme.h
@@ -15,8 +15,8 @@ Author(s):
--*/
#pragma once
-#include
#include
+#include "TerminalSettings.h"
#include "../../inc/conattrs.hpp"
// fwdecl unittest classes
@@ -38,7 +38,7 @@ class TerminalApp::ColorScheme
ColorScheme(std::wstring name, til::color defaultFg, til::color defaultBg, til::color cursorColor);
~ColorScheme();
- void ApplyScheme(winrt::Microsoft::Terminal::Settings::TerminalSettings terminalSettings) const;
+ void ApplyScheme(winrt::TerminalApp::TerminalSettings terminalSettings) const;
static ColorScheme FromJson(const Json::Value& json);
bool ShouldBeLayered(const Json::Value& json) const;
diff --git a/src/cascadia/TerminalApp/Command.cpp b/src/cascadia/TerminalApp/Command.cpp
index a8e72ec6a84..f3fe1219126 100644
--- a/src/cascadia/TerminalApp/Command.cpp
+++ b/src/cascadia/TerminalApp/Command.cpp
@@ -7,10 +7,12 @@
#include "Utils.h"
#include "ActionAndArgs.h"
+#include "JsonUtils.h"
#include
using namespace winrt::Microsoft::Terminal::Settings;
using namespace winrt::TerminalApp;
+using namespace ::TerminalApp;
static constexpr std::string_view NameKey{ "name" };
static constexpr std::string_view IconPathKey{ "iconPath" };
@@ -35,25 +37,17 @@ namespace winrt::TerminalApp::implementation
{
if (name.isObject())
{
- try
+ if (const auto resourceKey{ JsonUtils::GetValueForKey>(name, "key") })
{
- if (const auto keyJson{ name[JsonKey("key")] })
+ if (HasLibraryResourceWithName(*resourceKey))
{
- // Make sure the key is present before we try
- // loading it. Otherwise we'll crash
- const auto resourceKey = GetWstringFromJson(keyJson);
- if (HasLibraryResourceWithName(resourceKey))
- {
- return GetLibraryResourceString(resourceKey);
- }
+ return GetLibraryResourceString(*resourceKey);
}
}
- CATCH_LOG();
}
else if (name.isString())
{
- auto nameStr = name.asString();
- return winrt::to_hstring(nameStr);
+ return JsonUtils::GetValue(name);
}
}
diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp
index 6fac2d35d62..fd0a139281b 100644
--- a/src/cascadia/TerminalApp/CommandPalette.cpp
+++ b/src/cascadia/TerminalApp/CommandPalette.cpp
@@ -187,14 +187,16 @@ namespace winrt::TerminalApp::implementation
{
const auto actionAndArgs = command.Action();
_dispatch.DoAction(actionAndArgs);
- _close();
TraceLoggingWrite(
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
"CommandPaletteDispatchedAction",
TraceLoggingDescription("Event emitted when the user selects an action in the Command Palette"),
+ TraceLoggingUInt32(_searchBox().Text().size(), "SearchTextLength", "Number of characters in the search string"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
+
+ _close();
}
}
@@ -271,16 +273,17 @@ namespace winrt::TerminalApp::implementation
};
// Method Description:
- // - Update our list of filtered actions to reflect the current contents of
+ // - Produce a list of filtered actions to reflect the current contents of
// the input box. For more details on which commands will be displayed,
// see `_getWeight`.
// Arguments:
- // -
+ // - A collection that will receive the filtered actions
// Return Value:
// -
- void CommandPalette::_updateFilteredActions()
+ std::vector CommandPalette::_collectFilteredActions()
{
- _filteredActions.Clear();
+ std::vector actions;
+
auto searchText = _searchBox().Text();
const bool addAll = searchText.empty();
@@ -303,10 +306,10 @@ namespace winrt::TerminalApp::implementation
for (auto action : sortedCommands)
{
- _filteredActions.Append(action);
+ actions.push_back(action);
}
- return;
+ return actions;
}
// Here, there was some filter text.
@@ -343,7 +346,56 @@ namespace winrt::TerminalApp::implementation
{
auto top = heap.top();
heap.pop();
- _filteredActions.Append(top.command);
+ actions.push_back(top.command);
+ }
+
+ return actions;
+ }
+
+ // Method Description:
+ // - Update our list of filtered actions to reflect the current contents of
+ // the input box. For more details on which commands will be displayed,
+ // see `_getWeight`.
+ // Arguments:
+ // -
+ // Return Value:
+ // -
+ void CommandPalette::_updateFilteredActions()
+ {
+ auto actions = _collectFilteredActions();
+
+ // Make _filteredActions look identical to actions, using only Insert and Remove.
+ // This allows WinUI to nicely animate the ListView as it changes.
+ for (uint32_t i = 0; i < _filteredActions.Size() && i < actions.size(); i++)
+ {
+ for (uint32_t j = i; j < _filteredActions.Size(); j++)
+ {
+ if (_filteredActions.GetAt(j) == actions[i])
+ {
+ for (uint32_t k = i; k < j; k++)
+ {
+ _filteredActions.RemoveAt(i);
+ }
+ break;
+ }
+ }
+
+ if (_filteredActions.GetAt(i) != actions[i])
+ {
+ _filteredActions.InsertAt(i, actions[i]);
+ }
+ }
+
+ // Remove any extra trailing items from the destination
+ while (_filteredActions.Size() > actions.size())
+ {
+ _filteredActions.RemoveAtEnd();
+ }
+
+ // Add any extra trailing items from the source
+ while (_filteredActions.Size() < actions.size())
+ {
+ _filteredActions.Append(actions[_filteredActions.Size()]);
}
}
diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h
index 2d396070518..a14466f4f36 100644
--- a/src/cascadia/TerminalApp/CommandPalette.h
+++ b/src/cascadia/TerminalApp/CommandPalette.h
@@ -37,6 +37,7 @@ namespace winrt::TerminalApp::implementation
void _selectNextItem(const bool moveDown);
void _updateFilteredActions();
+ std::vector _collectFilteredActions();
static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name);
void _close();
diff --git a/src/cascadia/TerminalApp/CommandPalette.xaml b/src/cascadia/TerminalApp/CommandPalette.xaml
index e3d89d3e00a..39b0e1a25be 100644
--- a/src/cascadia/TerminalApp/CommandPalette.xaml
+++ b/src/cascadia/TerminalApp/CommandPalette.xaml
@@ -195,9 +195,10 @@ the MIT License. See LICENSE in the project root for license information. -->
-
-
-
+
+
+
+
diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.cpp b/src/cascadia/TerminalApp/GlobalAppSettings.cpp
index 1e074d86cf2..dd3b17ae723 100644
--- a/src/cascadia/TerminalApp/GlobalAppSettings.cpp
+++ b/src/cascadia/TerminalApp/GlobalAppSettings.cpp
@@ -7,11 +7,11 @@
#include "../../inc/DefaultSettings.h"
#include "Utils.h"
#include "JsonUtils.h"
+#include "TerminalSettingsSerializationHelpers.h"
using namespace TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings;
using namespace winrt::TerminalApp;
-using namespace winrt::Windows::Data::Json;
using namespace winrt::Windows::UI::Xaml;
using namespace ::Microsoft::Console;
using namespace winrt::Microsoft::UI::Xaml::Controls;
@@ -44,21 +44,6 @@ static constexpr std::string_view ForceFullRepaintRenderingKey{ "experimental.re
static constexpr std::string_view SoftwareRenderingKey{ "experimental.rendering.software" };
static constexpr std::string_view ForceVTInputKey{ "experimental.input.forceVT" };
-// Launch mode values
-static constexpr std::wstring_view DefaultLaunchModeValue{ L"default" };
-static constexpr std::wstring_view MaximizedLaunchModeValue{ L"maximized" };
-static constexpr std::wstring_view FullscreenLaunchModeValue{ L"fullscreen" };
-
-// Tab Width Mode values
-static constexpr std::wstring_view EqualTabWidthModeValue{ L"equal" };
-static constexpr std::wstring_view TitleLengthTabWidthModeValue{ L"titleLength" };
-static constexpr std::wstring_view TitleLengthCompactModeValue{ L"compact" };
-
-// Theme values
-static constexpr std::wstring_view LightThemeValue{ L"light" };
-static constexpr std::wstring_view DarkThemeValue{ L"dark" };
-static constexpr std::wstring_view SystemThemeValue{ L"system" };
-
#ifdef _DEBUG
static constexpr bool debugFeaturesDefault{ true };
#else
@@ -149,66 +134,51 @@ GlobalAppSettings GlobalAppSettings::FromJson(const Json::Value& json)
void GlobalAppSettings::LayerJson(const Json::Value& json)
{
- if (auto defaultProfile{ json[JsonKey(DefaultProfileKey)] })
- {
- _unparsedDefaultProfile.emplace(GetWstringFromJson(defaultProfile));
- }
+ JsonUtils::GetValueForKey(json, DefaultProfileKey, _unparsedDefaultProfile);
- JsonUtils::GetBool(json, AlwaysShowTabsKey, _AlwaysShowTabs);
+ JsonUtils::GetValueForKey(json, AlwaysShowTabsKey, _AlwaysShowTabs);
- JsonUtils::GetBool(json, ConfirmCloseAllKey, _ConfirmCloseAllTabs);
+ JsonUtils::GetValueForKey(json, ConfirmCloseAllKey, _ConfirmCloseAllTabs);
- JsonUtils::GetInt(json, InitialRowsKey, _InitialRows);
+ JsonUtils::GetValueForKey(json, InitialRowsKey, _InitialRows);
- JsonUtils::GetInt(json, InitialColsKey, _InitialCols);
+ JsonUtils::GetValueForKey(json, InitialColsKey, _InitialCols);
- if (auto initialPosition{ json[JsonKey(InitialPositionKey)] })
- {
- _ParseInitialPosition(initialPosition.asString(), _InitialPosition);
- }
+ JsonUtils::GetValueForKey(json, InitialPositionKey, _InitialPosition);
- JsonUtils::GetBool(json, ShowTitleInTitlebarKey, _ShowTitleInTitlebar);
+ JsonUtils::GetValueForKey(json, ShowTitleInTitlebarKey, _ShowTitleInTitlebar);
- JsonUtils::GetBool(json, ShowTabsInTitlebarKey, _ShowTabsInTitlebar);
+ JsonUtils::GetValueForKey(json, ShowTabsInTitlebarKey, _ShowTabsInTitlebar);
- JsonUtils::GetWstring(json, WordDelimitersKey, _WordDelimiters);
+ JsonUtils::GetValueForKey(json, WordDelimitersKey, _WordDelimiters);
- JsonUtils::GetBool(json, CopyOnSelectKey, _CopyOnSelect);
+ JsonUtils::GetValueForKey(json, CopyOnSelectKey, _CopyOnSelect);
- JsonUtils::GetBool(json, CopyFormattingKey, _CopyFormatting);
+ JsonUtils::GetValueForKey(json, CopyFormattingKey, _CopyFormatting);
- JsonUtils::GetBool(json, WarnAboutLargePasteKey, _WarnAboutLargePaste);
+ JsonUtils::GetValueForKey(json, WarnAboutLargePasteKey, _WarnAboutLargePaste);
- JsonUtils::GetBool(json, WarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste);
+ JsonUtils::GetValueForKey(json, WarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste);
- if (auto launchMode{ json[JsonKey(LaunchModeKey)] })
- {
- _LaunchMode = _ParseLaunchMode(GetWstringFromJson(launchMode));
- }
+ JsonUtils::GetValueForKey(json, LaunchModeKey, _LaunchMode);
- if (auto theme{ json[JsonKey(ThemeKey)] })
- {
- _Theme = _ParseTheme(GetWstringFromJson(theme));
- }
+ JsonUtils::GetValueForKey(json, ThemeKey, _Theme);
- if (auto tabWidthMode{ json[JsonKey(TabWidthModeKey)] })
- {
- _TabWidthMode = _ParseTabWidthMode(GetWstringFromJson(tabWidthMode));
- }
+ JsonUtils::GetValueForKey(json, TabWidthModeKey, _TabWidthMode);
- JsonUtils::GetBool(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
+ JsonUtils::GetValueForKey(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
- JsonUtils::GetBool(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering);
+ // GetValueForKey will only override the current value if the key exists
+ JsonUtils::GetValueForKey(json, DebugFeaturesKey, _DebugFeaturesEnabled);
- JsonUtils::GetBool(json, SoftwareRenderingKey, _SoftwareRendering);
- JsonUtils::GetBool(json, ForceVTInputKey, _ForceVTInput);
+ JsonUtils::GetValueForKey(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering);
- // GetBool will only override the current value if the key exists
- JsonUtils::GetBool(json, DebugFeaturesKey, _DebugFeaturesEnabled);
+ JsonUtils::GetValueForKey(json, SoftwareRenderingKey, _SoftwareRendering);
+ JsonUtils::GetValueForKey(json, ForceVTInputKey, _ForceVTInput);
- JsonUtils::GetBool(json, EnableStartupTaskKey, _StartOnUserLogin);
+ JsonUtils::GetValueForKey(json, EnableStartupTaskKey, _StartOnUserLogin);
- JsonUtils::GetBool(json, AlwaysOnTopKey, _AlwaysOnTop);
+ JsonUtils::GetValueForKey(json, AlwaysOnTopKey, _AlwaysOnTop);
// This is a helper lambda to get the keybindings and commands out of both
// and array of objects. We'll use this twice, once on the legacy
@@ -229,123 +199,12 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
warnings = winrt::TerminalApp::implementation::Command::LayerJson(_commands, bindings);
// It's possible that the user provided commands have some warnings
// in them, similar to the keybindings.
- _keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
}
};
parseBindings(LegacyKeybindingsKey);
parseBindings(BindingsKey);
}
-// Method Description:
-// - Helper function for converting a user-specified cursor style corresponding
-// CursorStyle enum value
-// Arguments:
-// - themeString: The string value from the settings file to parse
-// Return Value:
-// - The corresponding enum value which maps to the string provided by the user
-ElementTheme GlobalAppSettings::_ParseTheme(const std::wstring& themeString) noexcept
-{
- if (themeString == LightThemeValue)
- {
- return ElementTheme::Light;
- }
- else if (themeString == DarkThemeValue)
- {
- return ElementTheme::Dark;
- }
- // default behavior for invalid data or SystemThemeValue
- return ElementTheme::Default;
-}
-
-// Method Description:
-// - Helper function for converting the initial position string into
-// 2 coordinate values. We allow users to only provide one coordinate,
-// thus, we use comma as the separator:
-// (100, 100): standard input string
-// (, 100), (100, ): if a value is missing, we set this value as a default
-// (,): both x and y are set to default
-// (abc, 100): if a value is not valid, we treat it as default
-// (100, 100, 100): we only read the first two values, this is equivalent to (100, 100)
-// Arguments:
-// - initialPosition: the initial position string from json
-// ret: reference to a struct whose optionals will be populated
-// Return Value:
-// - None
-void GlobalAppSettings::_ParseInitialPosition(const std::string& initialPosition,
- LaunchPosition& ret) noexcept
-{
- static constexpr char singleCharDelim = ',';
- std::stringstream tokenStream(initialPosition);
- std::string token;
- uint8_t initialPosIndex = 0;
-
- // Get initial position values till we run out of delimiter separated values in the stream
- // or we hit max number of allowable values (= 2)
- // Non-numeral values or empty string will be caught as exception and we do not assign them
- for (; std::getline(tokenStream, token, singleCharDelim) && (initialPosIndex < 2); initialPosIndex++)
- {
- try
- {
- int32_t position = std::stoi(token);
- if (initialPosIndex == 0)
- {
- ret.x.emplace(position);
- }
-
- if (initialPosIndex == 1)
- {
- ret.y.emplace(position);
- }
- }
- catch (...)
- {
- // Do nothing
- }
- }
-}
-
-// Method Description:
-// - Helper function for converting the user-specified launch mode
-// to a LaunchMode enum value
-// Arguments:
-// - launchModeString: The string value from the settings file to parse
-// Return Value:
-// - The corresponding enum value which maps to the string provided by the user
-LaunchMode GlobalAppSettings::_ParseLaunchMode(const std::wstring& launchModeString) noexcept
-{
- if (launchModeString == MaximizedLaunchModeValue)
- {
- return LaunchMode::MaximizedMode;
- }
- else if (launchModeString == FullscreenLaunchModeValue)
- {
- return LaunchMode::FullscreenMode;
- }
-
- return LaunchMode::DefaultMode;
-}
-
-// Method Description:
-// - Helper function for converting the user-specified tab width
-// to a TabViewWidthMode enum value
-// Arguments:
-// - tabWidthModeString: The string value from the settings file to parse
-// Return Value:
-// - The corresponding enum value which maps to the string provided by the user
-TabViewWidthMode GlobalAppSettings::_ParseTabWidthMode(const std::wstring& tabWidthModeString) noexcept
-{
- if (tabWidthModeString == TitleLengthTabWidthModeValue)
- {
- return TabViewWidthMode::SizeToContent;
- }
- else if (tabWidthModeString == TitleLengthCompactModeValue)
- {
- return TabViewWidthMode::Compact;
- }
- // default behavior for invalid data or EqualTabWidthValue
- return TabViewWidthMode::Equal;
-}
-
// Method Description:
// - Adds the given colorscheme to our map of schemes, using its name as the key.
// Arguments:
diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.h b/src/cascadia/TerminalApp/GlobalAppSettings.h
index 988d7fe1c08..78222106716 100644
--- a/src/cascadia/TerminalApp/GlobalAppSettings.h
+++ b/src/cascadia/TerminalApp/GlobalAppSettings.h
@@ -17,6 +17,7 @@ Author(s):
#include "AppKeyBindings.h"
#include "ColorScheme.h"
#include "Command.h"
+#include "SettingsTypes.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
@@ -28,12 +29,6 @@ namespace TerminalAppLocalTests
namespace TerminalApp
{
class GlobalAppSettings;
-
- struct LaunchPosition
- {
- std::optional x;
- std::optional y;
- };
};
class TerminalApp::GlobalAppSettings final
@@ -51,7 +46,7 @@ class TerminalApp::GlobalAppSettings final
static GlobalAppSettings FromJson(const Json::Value& json);
void LayerJson(const Json::Value& json);
- void ApplyToSettings(winrt::Microsoft::Terminal::Settings::TerminalSettings& settings) const noexcept;
+ void ApplyToSettings(winrt::TerminalApp::TerminalSettings& settings) const noexcept;
std::vector GetKeybindingsWarnings() const;
@@ -96,15 +91,6 @@ class TerminalApp::GlobalAppSettings final
std::unordered_map _colorSchemes;
std::unordered_map _commands;
- static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;
-
- static winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode _ParseTabWidthMode(const std::wstring& tabWidthModeString) noexcept;
-
- static void _ParseInitialPosition(const std::string& initialPosition,
- LaunchPosition& ret) noexcept;
-
- static winrt::TerminalApp::LaunchMode _ParseLaunchMode(const std::wstring& launchModeString) noexcept;
-
friend class TerminalAppLocalTests::SettingsTests;
friend class TerminalAppLocalTests::ColorSchemeTests;
};
diff --git a/src/cascadia/TerminalApp/JsonUtils.cpp b/src/cascadia/TerminalApp/JsonUtils.cpp
deleted file mode 100644
index f4282097b83..00000000000
--- a/src/cascadia/TerminalApp/JsonUtils.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT license.
-
-#include "pch.h"
-#include "Utils.h"
-#include "JsonUtils.h"
-#include "../../types/inc/Utils.hpp"
-
-void TerminalApp::JsonUtils::GetOptionalColor(const Json::Value& json,
- std::string_view key,
- std::optional& target)
-{
- const auto conversionFn = [](const Json::Value& value) -> til::color {
- return ::Microsoft::Console::Utils::ColorFromHexString(value.asString());
- };
- GetOptionalValue(json,
- key,
- target,
- conversionFn);
-}
-
-void TerminalApp::JsonUtils::GetOptionalString(const Json::Value& json,
- std::string_view key,
- std::optional& target)
-{
- const auto conversionFn = [](const Json::Value& value) -> std::wstring {
- return GetWstringFromJson(value);
- };
- GetOptionalValue(json,
- key,
- target,
- conversionFn);
-}
-
-void TerminalApp::JsonUtils::GetOptionalGuid(const Json::Value& json,
- std::string_view key,
- std::optional& target)
-{
- const auto conversionFn = [](const Json::Value& value) -> GUID {
- return ::Microsoft::Console::Utils::GuidFromString(GetWstringFromJson(value));
- };
- GetOptionalValue(json,
- key,
- target,
- conversionFn);
-}
-
-void TerminalApp::JsonUtils::GetOptionalDouble(const Json::Value& json,
- std::string_view key,
- std::optional& target)
-{
- const auto conversionFn = [](const Json::Value& value) -> double {
- return value.asFloat();
- };
- const auto validationFn = [](const Json::Value& value) -> bool {
- return value.isNumeric();
- };
- GetOptionalValue(json,
- key,
- target,
- conversionFn,
- validationFn);
-}
-
-void TerminalApp::JsonUtils::GetInt(const Json::Value& json,
- std::string_view key,
- int& target)
-{
- const auto conversionFn = [](const Json::Value& value) -> int {
- return value.asInt();
- };
- const auto validationFn = [](const Json::Value& value) -> bool {
- return value.isInt();
- };
- GetValue(json, key, target, conversionFn, validationFn);
-}
-
-void TerminalApp::JsonUtils::GetUInt(const Json::Value& json,
- std::string_view key,
- uint32_t& target)
-{
- const auto conversionFn = [](const Json::Value& value) -> uint32_t {
- return value.asUInt();
- };
- const auto validationFn = [](const Json::Value& value) -> bool {
- return value.isUInt();
- };
- GetValue(json, key, target, conversionFn, validationFn);
-}
-
-void TerminalApp::JsonUtils::GetDouble(const Json::Value& json,
- std::string_view key,
- double& target)
-{
- const auto conversionFn = [](const Json::Value& value) -> double {
- return value.asFloat();
- };
- const auto validationFn = [](const Json::Value& value) -> bool {
- return value.isNumeric();
- };
- GetValue(json, key, target, conversionFn, validationFn);
-}
-
-void TerminalApp::JsonUtils::GetBool(const Json::Value& json,
- std::string_view key,
- bool& target)
-{
- const auto conversionFn = [](const Json::Value& value) -> bool {
- return value.asBool();
- };
- const auto validationFn = [](const Json::Value& value) -> bool {
- return value.isBool();
- };
- GetValue(json, key, target, conversionFn, validationFn);
-}
-
-void TerminalApp::JsonUtils::GetWstring(const Json::Value& json,
- std::string_view key,
- std::wstring& target)
-{
- const auto conversionFn = [](const Json::Value& value) -> std::wstring {
- return GetWstringFromJson(value);
- };
- GetValue(json, key, target, conversionFn, nullptr);
-}
diff --git a/src/cascadia/TerminalApp/JsonUtils.h b/src/cascadia/TerminalApp/JsonUtils.h
index 1845a9d6524..b98ade1da9d 100644
--- a/src/cascadia/TerminalApp/JsonUtils.h
+++ b/src/cascadia/TerminalApp/JsonUtils.h
@@ -9,136 +9,483 @@ Module Name:
- Helpers for the TerminalApp project
Author(s):
- Mike Griese - August 2019
-
+- Dustin Howett - January 2020
--*/
+
#pragma once
+#include
+
+#include "../types/inc/utils.hpp"
+
+namespace winrt
+{
+ // If we don't use winrt, nobody will include the ConversionTraits for winrt stuff.
+ // If nobody includes it, these forward declarations will suffice.
+ struct guid;
+ struct hstring;
+ namespace Windows::Foundation
+ {
+ template
+ struct IReference;
+ }
+}
+
namespace TerminalApp::JsonUtils
{
- void GetOptionalColor(const Json::Value& json,
- std::string_view key,
- std::optional& target);
+ namespace Detail
+ {
+ // Function Description:
+ // - Returns a string_view to a Json::Value's internal string storage,
+ // hopefully without copying it.
+ __declspec(noinline) inline const std::string_view GetStringView(const Json::Value& json)
+ {
+ const char* begin{ nullptr };
+ const char* end{ nullptr };
+ json.getString(&begin, &end);
+ const std::string_view zeroCopyString{ begin, gsl::narrow_cast(end - begin) };
+ return zeroCopyString;
+ }
- void GetOptionalString(const Json::Value& json,
- std::string_view key,
- std::optional& target);
+ template
+ struct DeduceOptional
+ {
+ using Type = typename std::decay::type;
+ static constexpr bool IsOptional = false;
+ };
- void GetOptionalGuid(const Json::Value& json,
- std::string_view key,
- std::optional& target);
+ template
+ struct DeduceOptional>
+ {
+ using Type = typename std::decay::type;
+ static constexpr bool IsOptional = true;
+ };
- void GetOptionalDouble(const Json::Value& json,
- std::string_view key,
- std::optional& target);
+ template
+ struct DeduceOptional<::winrt::Windows::Foundation::IReference>
+ {
+ using Type = typename std::decay::type;
+ static constexpr bool IsOptional = true;
+ };
+ }
- // Method Description:
- // - Helper that can be used for retrieving an optional value from a json
- // object, and parsing it's value to layer on a given target object.
- // - If the key we're looking for _doesn't_ exist in the json object,
- // we'll leave the target object unmodified.
- // - If the key exists in the json object, but is set to `null`, then
- // we'll instead set the target back to nullopt.
- // - Each caller should provide a conversion function that takes a
- // Json::Value and returns an object of the same type as target.
- // Arguments:
- // - json: The json object to search for the given key
- // - key: The key to look for in the json object
- // - target: the optional object to receive the value from json
- // - conversion: a std::function which can be used to
- // convert the Json::Value to the appropriate type.
- // - validation: optional, if provided, will be called first to ensure that
- // the json::value is of the correct type before attempting to call
- // `conversion`.
- // Return Value:
- // -
- template
- void GetOptionalValue(const Json::Value& json,
- std::string_view key,
- std::optional& target,
- F&& conversion,
- const std::function& validation = nullptr)
+ // These exceptions cannot use localized messages, as we do not have
+ // guaranteed access to the resource loader.
+ class TypeMismatchException : public std::runtime_error
+ {
+ public:
+ TypeMismatchException() :
+ runtime_error("unexpected data type") {}
+ };
+
+ class KeyedException : public std::runtime_error
+ {
+ public:
+ KeyedException(const std::string_view key, std::exception_ptr exception) :
+ runtime_error(fmt::format("error parsing \"{0}\"", key).c_str()),
+ _key{ key },
+ _innerException{ std::move(exception) } {}
+
+ std::string GetKey() const
+ {
+ return _key;
+ }
+
+ [[noreturn]] void RethrowInner() const
+ {
+ std::rethrow_exception(_innerException);
+ }
+
+ private:
+ std::string _key;
+ std::exception_ptr _innerException;
+ };
+
+ class UnexpectedValueException : public std::runtime_error
+ {
+ public:
+ UnexpectedValueException(const std::string_view value) :
+ runtime_error(fmt::format("unexpected value \"{0}\"", value).c_str()),
+ _value{ value } {}
+
+ std::string GetValue() const
+ {
+ return _value;
+ }
+
+ private:
+ std::string _value;
+ };
+
+ template
+ struct ConversionTrait
+ {
+ // Forward-declare these so the linker can pick up specializations from elsewhere!
+ T FromJson(const Json::Value&);
+ bool CanConvert(const Json::Value& json);
+ };
+
+ template<>
+ struct ConversionTrait
+ {
+ std::string FromJson(const Json::Value& json)
+ {
+ return json.asString();
+ }
+
+ bool CanConvert(const Json::Value& json)
+ {
+ return json.isString();
+ }
+ };
+
+ template<>
+ struct ConversionTrait
+ {
+ std::wstring FromJson(const Json::Value& json)
+ {
+ return til::u8u16(Detail::GetStringView(json));
+ }
+
+ bool CanConvert(const Json::Value& json)
+ {
+ return json.isString();
+ }
+ };
+
+#ifdef WINRT_BASE_H
+ template<>
+ struct ConversionTrait : public ConversionTrait
+ {
+ // Leverage the wstring converter's validation
+ winrt::hstring FromJson(const Json::Value& json)
+ {
+ return winrt::hstring{ til::u8u16(Detail::GetStringView(json)) };
+ }
+ };
+#endif
+
+ template<>
+ struct ConversionTrait
+ {
+ bool FromJson(const Json::Value& json)
+ {
+ return json.asBool();
+ }
+
+ bool CanConvert(const Json::Value& json)
+ {
+ return json.isBool();
+ }
+ };
+
+ template<>
+ struct ConversionTrait
+ {
+ int FromJson(const Json::Value& json)
+ {
+ return json.asInt();
+ }
+
+ bool CanConvert(const Json::Value& json)
+ {
+ return json.isInt();
+ }
+ };
+
+ template<>
+ struct ConversionTrait
+ {
+ unsigned int FromJson(const Json::Value& json)
+ {
+ return json.asUInt();
+ }
+
+ bool CanConvert(const Json::Value& json)
+ {
+ return json.isUInt();
+ }
+ };
+
+ template<>
+ struct ConversionTrait
+ {
+ float FromJson(const Json::Value& json)
+ {
+ return json.asFloat();
+ }
+
+ bool CanConvert(const Json::Value& json)
+ {
+ return json.isNumeric();
+ }
+ };
+
+ template<>
+ struct ConversionTrait
+ {
+ double FromJson(const Json::Value& json)
+ {
+ return json.asDouble();
+ }
+
+ bool CanConvert(const Json::Value& json)
+ {
+ return json.isNumeric();
+ }
+ };
+
+ template<>
+ struct ConversionTrait
+ {
+ GUID FromJson(const Json::Value& json)
+ {
+ return ::Microsoft::Console::Utils::GuidFromString(til::u8u16(Detail::GetStringView(json)));
+ }
+
+ bool CanConvert(const Json::Value& json)
+ {
+ if (!json.isString())
+ {
+ return false;
+ }
+
+ const auto string{ Detail::GetStringView(json) };
+ return string.length() == 38 && string.front() == '{' && string.back() == '}';
+ }
+ };
+
+ // (GUID and winrt::guid are mutually convertible!)
+ template<>
+ struct ConversionTrait : public ConversionTrait
+ {
+ };
+
+ template<>
+ struct ConversionTrait
+ {
+ til::color FromJson(const Json::Value& json)
+ {
+ return ::Microsoft::Console::Utils::ColorFromHexString(Detail::GetStringView(json));
+ }
+
+ bool CanConvert(const Json::Value& json)
+ {
+ if (!json.isString())
+ {
+ return false;
+ }
+
+ const auto string{ Detail::GetStringView(json) };
+ return (string.length() == 7 || string.length() == 4) && string.front() == '#';
+ }
+ };
+
+ template
+ struct EnumMapper
{
- if (json.isMember(JsonKey(key)))
+ using BaseEnumMapper = EnumMapper;
+ using ValueType = T;
+ using pair_type = std::pair;
+ T FromJson(const Json::Value& json)
{
- if (auto jsonVal{ json[JsonKey(key)] })
+ const auto name{ Detail::GetStringView(json) };
+ for (const auto& pair : TBase::mappings)
{
- if (validation == nullptr || validation(jsonVal))
+ if (pair.first == name)
{
- target = conversion(jsonVal);
+ return pair.second;
}
}
- else
+
+ throw UnexpectedValueException{ name };
+ }
+
+ bool CanConvert(const Json::Value& json)
+ {
+ return json.isString();
+ }
+ };
+
+ // FlagMapper is EnumMapper, but it works for bitfields.
+ // It supports a string (single flag) or an array of strings.
+ // Does an O(n*m) search; meant for small search spaces!
+ //
+ // Cleverly leverage EnumMapper to do the heavy lifting.
+ template
+ struct FlagMapper : public EnumMapper
+ {
+ private:
+ // Hide BaseEnumMapper so FlagMapper's consumers cannot see
+ // it.
+ using BaseEnumMapper = EnumMapper::BaseEnumMapper;
+
+ public:
+ using BaseFlagMapper = FlagMapper;
+ static constexpr T AllSet{ static_cast(~0u) };
+ static constexpr T AllClear{ static_cast(0u) };
+
+ T FromJson(const Json::Value& json)
+ {
+ if (json.isString())
+ {
+ return BaseEnumMapper::FromJson(json);
+ }
+ else if (json.isArray())
{
- // This branch is hit when the json object contained the key,
- // but the key was set to `null`. In this case, explicitly clear
- // the target.
- target = std::nullopt;
+ unsigned int seen{ 0 };
+ T value{};
+ for (const auto& element : json)
+ {
+ const auto newFlag{ BaseEnumMapper::FromJson(element) };
+ if (++seen > 1 &&
+ ((newFlag == AllClear && value != AllClear) ||
+ (value == AllClear && newFlag != AllClear)))
+ {
+ // attempt to combine AllClear (explicitly) with anything else
+ throw UnexpectedValueException{ element.asString() };
+ }
+ value |= newFlag;
+ }
+ return value;
}
+
+ // We'll only get here if CanConvert has failed us.
+ return AllClear;
}
- }
+
+ bool CanConvert(const Json::Value& json)
+ {
+ return BaseEnumMapper::CanConvert(json) || json.isArray();
+ }
+ };
// Method Description:
- // - Helper that can be used for retrieving a value from a json
- // object, and parsing it's value to set on a given target object.
- // - If the key we're looking for _doesn't_ exist in the json object,
- // we'll leave the target object unmodified.
- // - If the key exists in the json object, we'll use the provided
- // `validation` function to ensure that the json value is of the
- // correct type.
- // - If we successfully validate the json value type (or no validation
- // function was provided), then we'll use `conversion` to parse the
- // value and place the result into `target`
- // - Each caller should provide a conversion function that takes a
- // Json::Value and returns an object of the same type as target.
- // - Unlike GetOptionalValue, if the key exists but is set to `null`, we'll
- // just ignore it.
+ // - Helper that will populate a reference with a value converted from a json object.
// Arguments:
- // - json: The json object to search for the given key
- // - key: The key to look for in the json object
- // - target: the optional object to receive the value from json
- // - conversion: a std::function which can be used to
- // convert the Json::Value to the appropriate type.
- // - validation: optional, if provided, will be called first to ensure that
- // the json::value is of the correct type before attempting to call
- // `conversion`.
+ // - json: the json object to convert
+ // - target: the value to populate with the converted result
// Return Value:
- // -
- template
- void GetValue(const Json::Value& json,
- std::string_view key,
- T& target,
- F&& conversion,
- const std::function& validation = nullptr)
+ // - a boolean indicating whether the value existed (in this case, was non-null)
+ //
+ // GetValue, type-deduced, manual converter
+ template
+ bool GetValue(const Json::Value& json, T& target, Converter&& conv)
{
- if (json.isMember(JsonKey(key)))
+ if constexpr (Detail::DeduceOptional::IsOptional)
{
- if (auto jsonVal{ json[JsonKey(key)] })
+ // FOR OPTION TYPES
+ // - If the json object is set to `null`, then
+ // we'll instead set the target back to the empty optional.
+ if (json.isNull())
{
- if (validation == nullptr || validation(jsonVal))
- {
- target = conversion(jsonVal);
- }
+ target = T{}; // zero-construct an empty optional
+ return true;
+ }
+ }
+
+ if (json)
+ {
+ if (!conv.CanConvert(json))
+ {
+ throw TypeMismatchException{};
+ }
+
+ target = conv.FromJson(json);
+ return true;
+ }
+ return false;
+ }
+
+ // GetValue, forced return type, manual converter
+ template
+ std::decay_t GetValue(const Json::Value& json, Converter&& conv)
+ {
+ std::decay_t local{};
+ GetValue(json, local, std::forward(conv));
+ return local; // returns zero-initialized or value
+ }
+
+ // GetValueForKey, type-deduced, manual converter
+ template
+ bool GetValueForKey(const Json::Value& json, std::string_view key, T& target, Converter&& conv)
+ {
+ if (auto found{ json.find(&*key.cbegin(), (&*key.cbegin()) + key.size()) })
+ {
+ try
+ {
+ return GetValue(*found, target, std::forward(conv));
+ }
+ catch (...)
+ {
+ // Wrap any caught exceptions in one that preserves context.
+ throw KeyedException(key, std::current_exception());
}
}
+ return false;
}
- void GetInt(const Json::Value& json,
- std::string_view key,
- int& target);
+ // GetValueForKey, forced return type, manual converter
+ template
+ std::decay_t GetValueForKey(const Json::Value& json, std::string_view key, Converter&& conv)
+ {
+ std::decay_t local{};
+ GetValueForKey(json, key, local, std::forward(conv));
+ return local; // returns zero-initialized?
+ }
- void GetUInt(const Json::Value& json,
- std::string_view key,
- uint32_t& target);
+ // GetValue, type-deduced, with automatic converter
+ template
+ bool GetValue(const Json::Value& json, T& target)
+ {
+ return GetValue(json, target, ConversionTrait::Type>{});
+ }
- void GetDouble(const Json::Value& json,
- std::string_view key,
- double& target);
+ // GetValue, forced return type, with automatic converter
+ template
+ std::decay_t GetValue(const Json::Value& json)
+ {
+ std::decay_t local{};
+ GetValue(json, local, ConversionTrait::Type>{});
+ return local; // returns zero-initialized or value
+ }
- void GetBool(const Json::Value& json,
- std::string_view key,
- bool& target);
+ // GetValueForKey, type-deduced, with automatic converter
+ template
+ bool GetValueForKey(const Json::Value& json, std::string_view key, T& target)
+ {
+ return GetValueForKey(json, key, target, ConversionTrait::Type>{});
+ }
+
+ // GetValueForKey, forced return type, with automatic converter
+ template
+ std::decay_t GetValueForKey(const Json::Value& json, std::string_view key)
+ {
+ return GetValueForKey(json, key, ConversionTrait::Type>{});
+ }
+
+ // Get multiple values for keys (json, k, &v, k, &v, k, &v, ...).
+ // Uses the default converter for each v.
+ // Careful: this can cause a template explosion.
+ constexpr void GetValuesForKeys(const Json::Value& /*json*/) {}
- void GetWstring(const Json::Value& json,
- std::string_view key,
- std::wstring& target);
+ template
+ void GetValuesForKeys(const Json::Value& json, std::string_view key1, T&& val1, Args&&... args)
+ {
+ GetValueForKey(json, key1, val1);
+ GetValuesForKeys(json, std::forward(args)...);
+ }
};
+
+#define JSON_ENUM_MAPPER(...) \
+ template<> \
+ struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
+ public ::TerminalApp::JsonUtils::EnumMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
+
+#define JSON_FLAG_MAPPER(...) \
+ template<> \
+ struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
+ public ::TerminalApp::JsonUtils::FlagMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
+
+#define JSON_MAPPINGS(Count) \
+ static constexpr std::array mappings
diff --git a/src/cascadia/TerminalApp/JsonUtilsNew.h b/src/cascadia/TerminalApp/JsonUtilsNew.h
deleted file mode 100644
index 03e9826f4b2..00000000000
--- a/src/cascadia/TerminalApp/JsonUtilsNew.h
+++ /dev/null
@@ -1,490 +0,0 @@
-/*++
-Copyright (c) Microsoft Corporation
-Licensed under the MIT license.
-
-Module Name:
-- JsonUtils.h
-
-Abstract:
-- Helpers for the TerminalApp project
-Author(s):
-- Mike Griese - August 2019
-- Dustin Howett - January 2020
---*/
-
-#pragma once
-
-#include
-
-#include "../types/inc/utils.hpp"
-
-namespace winrt
-{
- // If we don't use winrt, nobody will include the ConversionTraits for winrt stuff.
- // If nobody includes it, these forward declarations will suffice.
- struct guid;
- struct hstring;
- namespace Windows::Foundation
- {
- template
- struct IReference;
- }
-}
-
-namespace TerminalApp::JsonUtils
-{
- namespace Detail
- {
- // Function Description:
- // - Returns a string_view to a Json::Value's internal string storage,
- // hopefully without copying it.
- __declspec(noinline) inline const std::string_view GetStringView(const Json::Value& json)
- {
- const char* begin{ nullptr };
- const char* end{ nullptr };
- json.getString(&begin, &end);
- const std::string_view zeroCopyString{ begin, gsl::narrow_cast(end - begin) };
- return zeroCopyString;
- }
-
- template
- struct DeduceOptional
- {
- using Type = typename std::decay::type;
- static constexpr bool IsOptional = false;
- };
-
- template
- struct DeduceOptional>
- {
- using Type = typename std::decay::type;
- static constexpr bool IsOptional = true;
- };
-
- template
- struct DeduceOptional<::winrt::Windows::Foundation::IReference>
- {
- using Type = typename std::decay::type;
- static constexpr bool IsOptional = true;
- };
- }
-
- // These exceptions cannot use localized messages, as we do not have
- // guaranteed access to the resource loader.
- class TypeMismatchException : public std::runtime_error
- {
- public:
- TypeMismatchException() :
- runtime_error("unexpected data type") {}
- };
-
- class KeyedException : public std::runtime_error
- {
- public:
- KeyedException(const std::string_view key, std::exception_ptr exception) :
- runtime_error(fmt::format("error parsing \"{0}\"", key).c_str()),
- _key{ key },
- _innerException{ std::move(exception) } {}
-
- std::string GetKey() const
- {
- return _key;
- }
-
- [[noreturn]] void RethrowInner() const
- {
- std::rethrow_exception(_innerException);
- }
-
- private:
- std::string _key;
- std::exception_ptr _innerException;
- };
-
- class UnexpectedValueException : public std::runtime_error
- {
- public:
- UnexpectedValueException(const std::string_view value) :
- runtime_error(fmt::format("unexpected value \"{0}\"", value).c_str()),
- _value{ value } {}
-
- std::string GetValue() const
- {
- return _value;
- }
-
- private:
- std::string _value;
- };
-
- template
- struct ConversionTrait
- {
- // Forward-declare these so the linker can pick up specializations from elsewhere!
- T FromJson(const Json::Value&);
- bool CanConvert(const Json::Value& json);
- };
-
- template<>
- struct ConversionTrait
- {
- std::string FromJson(const Json::Value& json)
- {
- return json.asString();
- }
-
- bool CanConvert(const Json::Value& json)
- {
- return json.isString();
- }
- };
-
- template<>
- struct ConversionTrait
- {
- std::wstring FromJson(const Json::Value& json)
- {
- return til::u8u16(Detail::GetStringView(json));
- }
-
- bool CanConvert(const Json::Value& json)
- {
- return json.isString();
- }
- };
-
-#ifdef WINRT_BASE_H
- template<>
- struct ConversionTrait : public ConversionTrait
- {
- // Leverage the wstring converter's validation
- winrt::hstring FromJson(const Json::Value& json)
- {
- return winrt::hstring{ til::u8u16(Detail::GetStringView(json)) };
- }
- };
-#endif
-
- template<>
- struct ConversionTrait
- {
- bool FromJson(const Json::Value& json)
- {
- return json.asBool();
- }
-
- bool CanConvert(const Json::Value& json)
- {
- return json.isBool();
- }
- };
-
- template<>
- struct ConversionTrait
- {
- int FromJson(const Json::Value& json)
- {
- return json.asInt();
- }
-
- bool CanConvert(const Json::Value& json)
- {
- return json.isInt();
- }
- };
-
- template<>
- struct ConversionTrait
- {
- unsigned int FromJson(const Json::Value& json)
- {
- return json.asUInt();
- }
-
- bool CanConvert(const Json::Value& json)
- {
- return json.isUInt();
- }
- };
-
- template<>
- struct ConversionTrait
- {
- float FromJson(const Json::Value& json)
- {
- return json.asFloat();
- }
-
- bool CanConvert(const Json::Value& json)
- {
- return json.isNumeric();
- }
- };
-
- template<>
- struct ConversionTrait
- {
- double FromJson(const Json::Value& json)
- {
- return json.asDouble();
- }
-
- bool CanConvert(const Json::Value& json)
- {
- return json.isNumeric();
- }
- };
-
- template<>
- struct ConversionTrait
- {
- GUID FromJson(const Json::Value& json)
- {
- return ::Microsoft::Console::Utils::GuidFromString(til::u8u16(Detail::GetStringView(json)));
- }
-
- bool CanConvert(const Json::Value& json)
- {
- if (!json.isString())
- {
- return false;
- }
-
- const auto string{ Detail::GetStringView(json) };
- return string.length() == 38 && string.front() == '{' && string.back() == '}';
- }
- };
-
- // (GUID and winrt::guid are mutually convertible!)
- template<>
- struct ConversionTrait : public ConversionTrait
- {
- };
-
- template<>
- struct ConversionTrait
- {
- til::color FromJson(const Json::Value& json)
- {
- return ::Microsoft::Console::Utils::ColorFromHexString(Detail::GetStringView(json));
- }
-
- bool CanConvert(const Json::Value& json)
- {
- if (!json.isString())
- {
- return false;
- }
-
- const auto string{ Detail::GetStringView(json) };
- return (string.length() == 7 || string.length() == 4) && string.front() == '#';
- }
- };
-
- template
- struct EnumMapper
- {
- using BaseEnumMapper = EnumMapper;
- using pair_type = std::pair;
- T FromJson(const Json::Value& json)
- {
- const auto name{ Detail::GetStringView(json) };
- for (const auto& pair : TBase::mappings)
- {
- if (pair.first == name)
- {
- return pair.second;
- }
- }
-
- throw UnexpectedValueException{ name };
- }
-
- bool CanConvert(const Json::Value& json)
- {
- return json.isString();
- }
- };
-
- // FlagMapper is EnumMapper, but it works for bitfields.
- // It supports a string (single flag) or an array of strings.
- // Does an O(n*m) search; meant for small search spaces!
- //
- // Cleverly leverage EnumMapper to do the heavy lifting.
- template
- struct FlagMapper : public EnumMapper
- {
- private:
- // Hide BaseEnumMapper so FlagMapper's consumers cannot see
- // it.
- using BaseEnumMapper = EnumMapper::BaseEnumMapper;
-
- public:
- using BaseFlagMapper = FlagMapper;
- static constexpr T AllSet{ static_cast(~0u) };
- static constexpr T AllClear{ static_cast(0u) };
-
- T FromJson(const Json::Value& json)
- {
- if (json.isString())
- {
- return BaseEnumMapper::FromJson(json);
- }
- else if (json.isArray())
- {
- unsigned int seen{ 0 };
- T value{};
- for (const auto& element : json)
- {
- const auto newFlag{ BaseEnumMapper::FromJson(element) };
- if (++seen > 1 &&
- ((newFlag == AllClear && value != AllClear) ||
- (value == AllClear && newFlag != AllClear)))
- {
- // attempt to combine AllClear (explicitly) with anything else
- throw UnexpectedValueException{ element.asString() };
- }
- value |= newFlag;
- }
- return value;
- }
-
- // We'll only get here if CanConvert has failed us.
- return AllClear;
- }
-
- bool CanConvert(const Json::Value& json)
- {
- return BaseEnumMapper::CanConvert(json) || json.isArray();
- }
- };
-
- // Method Description:
- // - Helper that will populate a reference with a value converted from a json object.
- // Arguments:
- // - json: the json object to convert
- // - target: the value to populate with the converted result
- // Return Value:
- // - a boolean indicating whether the value existed (in this case, was non-null)
- //
- // GetValue, type-deduced, manual converter
- template
- bool GetValue(const Json::Value& json, T& target, Converter&& conv)
- {
- if constexpr (Detail::DeduceOptional::IsOptional)
- {
- // FOR OPTION TYPES
- // - If the json object is set to `null`, then
- // we'll instead set the target back to the empty optional.
- if (json.isNull())
- {
- target = T{}; // zero-construct an empty optional
- return true;
- }
- }
-
- if (json)
- {
- if (!conv.CanConvert(json))
- {
- throw TypeMismatchException{};
- }
-
- target = conv.FromJson(json);
- return true;
- }
- return false;
- }
-
- // GetValue, forced return type, manual converter
- template
- std::decay_t GetValue(const Json::Value& json, Converter&& conv)
- {
- std::decay_t local{};
- GetValue(json, local, std::forward(conv));
- return local; // returns zero-initialized or value
- }
-
- // GetValueForKey, type-deduced, manual converter
- template
- bool GetValueForKey(const Json::Value& json, std::string_view key, T& target, Converter&& conv)
- {
- if (auto found{ json.find(&*key.cbegin(), (&*key.cbegin()) + key.size()) })
- {
- try
- {
- return GetValue(*found, target, std::forward(conv));
- }
- catch (...)
- {
- // Wrap any caught exceptions in one that preserves context.
- throw KeyedException(key, std::current_exception());
- }
- }
- return false;
- }
-
- // GetValueForKey, forced return type, manual converter
- template
- std::decay_t GetValueForKey(const Json::Value& json, std::string_view key, Converter&& conv)
- {
- std::decay_t local{};
- GetValueForKey(json, key, local, std::forward(conv));
- return local; // returns zero-initialized?
- }
-
- // GetValue, type-deduced, with automatic converter
- template
- bool GetValue(const Json::Value& json, T& target)
- {
- return GetValue(json, target, ConversionTrait::Type>{});
- }
-
- // GetValue, forced return type, with automatic converter
- template
- std::decay_t GetValue(const Json::Value& json)
- {
- std::decay_t local{};
- GetValue(json, local, ConversionTrait::Type>{});
- return local; // returns zero-initialized or value
- }
-
- // GetValueForKey, type-deduced, with automatic converter
- template
- bool GetValueForKey(const Json::Value& json, std::string_view key, T& target)
- {
- return GetValueForKey(json, key, target, ConversionTrait::Type>{});
- }
-
- // GetValueForKey, forced return type, with automatic converter
- template
- std::decay_t GetValueForKey(const Json::Value& json, std::string_view key)
- {
- return GetValueForKey(json, key, ConversionTrait::Type>{});
- }
-
- // Get multiple values for keys (json, k, &v, k, &v, k, &v, ...).
- // Uses the default converter for each v.
- // Careful: this can cause a template explosion.
- constexpr void GetValuesForKeys(const Json::Value& /*json*/) {}
-
- template
- void GetValuesForKeys(const Json::Value& json, std::string_view key1, T&& val1, Args&&... args)
- {
- GetValueForKey(json, key1, val1);
- GetValuesForKeys(json, std::forward(args)...);
- }
-};
-
-#define JSON_ENUM_MAPPER(...) \
- template<> \
- struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
- public ::TerminalApp::JsonUtils::EnumMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
-
-#define JSON_FLAG_MAPPER(...) \
- template<> \
- struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
- public ::TerminalApp::JsonUtils::FlagMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
-
-#define JSON_MAPPINGS(Count) \
- static constexpr std::array mappings
diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp
index b931755c1e3..f8e61ae8379 100644
--- a/src/cascadia/TerminalApp/Pane.cpp
+++ b/src/cascadia/TerminalApp/Pane.cpp
@@ -7,6 +7,7 @@
#include "CascadiaSettings.h"
using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Graphics::Display;
using namespace winrt::Windows::UI;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Core;
@@ -921,6 +922,102 @@ bool Pane::CanSplit(SplitState splitType)
return false;
}
+// Method Description:
+// - This is a helper to determine if a given Pane can be split, but without
+// using the ActualWidth() and ActualHeight() methods. This is used during
+// processing of many "split-pane" commands, which could happen _before_ we've
+// laid out a Pane for the first time. When this happens, the Pane's don't
+// have an actual size yet. However, we'd still like to figure out if the pane
+// could be split, once they're all laid out.
+// - This method assumes that the Pane we're attempting to split is `target`,
+// and this method should be called on the root of a tree of Panes.
+// - We'll walk down the tree attempting to find `target`. As we traverse the
+// tree, we'll reduce the size passed to each subsequent recursive call. The
+// size passed to this method represents how much space this Pane _will_ have
+// to use.
+// * If this pane is a leaf, and it's the pane we're looking for, use the
+// available space to calculate which direction to split in.
+// * If this pane is _any other leaf_, then just return nullopt, to indicate
+// that the `target` Pane is not down this branch.
+// * If this pane is a parent, calculate how much space our children will be
+// able to use, and recurse into them.
+// Arguments:
+// - target: The Pane we're attempting to split.
+// - splitType: The direction we're attempting to split in.
+// - availableSpace: The theoretical space that's available for this pane to be able to split.
+// Return Value:
+// - nullopt if `target` is not this pane or a child of this pane, otherwise
+// true iff we could split this pane, given `availableSpace`
+// Note:
+// - This method is highly similar to Pane::PreCalculateAutoSplit
+std::optional Pane::PreCalculateCanSplit(const std::shared_ptr target,
+ SplitState splitType,
+ const winrt::Windows::Foundation::Size availableSpace) const
+{
+ if (_IsLeaf())
+ {
+ if (target.get() == this)
+ {
+ // If this pane is a leaf, and it's the pane we're looking for, use
+ // the available space to calculate which direction to split in.
+ const Size minSize = _GetMinSize();
+
+ if (splitType == SplitState::None)
+ {
+ return { false };
+ }
+
+ else if (splitType == SplitState::Vertical)
+ {
+ const auto widthMinusSeparator = availableSpace.Width - CombinedPaneBorderSize;
+ const auto newWidth = widthMinusSeparator * Half;
+
+ return { newWidth > minSize.Width };
+ }
+
+ else if (splitType == SplitState::Horizontal)
+ {
+ const auto heightMinusSeparator = availableSpace.Height - CombinedPaneBorderSize;
+ const auto newHeight = heightMinusSeparator * Half;
+
+ return { newHeight > minSize.Height };
+ }
+ }
+ else
+ {
+ // If this pane is _any other leaf_, then just return nullopt, to
+ // indicate that the `target` Pane is not down this branch.
+ return std::nullopt;
+ }
+ }
+ else
+ {
+ // If this pane is a parent, calculate how much space our children will
+ // be able to use, and recurse into them.
+
+ const bool isVerticalSplit = _splitState == SplitState::Vertical;
+ const float firstWidth = isVerticalSplit ?
+ (availableSpace.Width * _desiredSplitPosition) - PaneBorderSize :
+ availableSpace.Width;
+ const float secondWidth = isVerticalSplit ?
+ (availableSpace.Width - firstWidth) - PaneBorderSize :
+ availableSpace.Width;
+ const float firstHeight = !isVerticalSplit ?
+ (availableSpace.Height * _desiredSplitPosition) - PaneBorderSize :
+ availableSpace.Height;
+ const float secondHeight = !isVerticalSplit ?
+ (availableSpace.Height - firstHeight) - PaneBorderSize :
+ availableSpace.Height;
+
+ const auto firstResult = _firstChild->PreCalculateCanSplit(target, splitType, { firstWidth, firstHeight });
+ return firstResult.has_value() ? firstResult : _secondChild->PreCalculateCanSplit(target, splitType, { secondWidth, secondHeight });
+ }
+
+ // We should not possibly be getting here - both the above branches should
+ // return a value.
+ FAIL_FAST();
+}
+
// Method Description:
// - Split the focused pane in our tree of panes, and place the given
// TermControl into the newly created pane. If we're the focused pane, then
diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h
index 7041db1f0cb..dfbcf1e7bc2 100644
--- a/src/cascadia/TerminalApp/Pane.h
+++ b/src/cascadia/TerminalApp/Pane.h
@@ -51,7 +51,7 @@ class Pane : public std::enable_shared_from_this
void ClearActive();
void SetActive();
- void UpdateSettings(const winrt::Microsoft::Terminal::Settings::TerminalSettings& settings,
+ void UpdateSettings(const winrt::TerminalApp::TerminalSettings& settings,
const GUID& profile);
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
void Relayout();
@@ -64,7 +64,9 @@ class Pane : public std::enable_shared_from_this
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
std::optional PreCalculateAutoSplit(const std::shared_ptr target, const winrt::Windows::Foundation::Size parentSize) const;
-
+ std::optional