diff --git a/src/buffer/out/Marks.hpp b/src/buffer/out/Marks.hpp new file mode 100644 index 00000000000..655416f8354 --- /dev/null +++ b/src/buffer/out/Marks.hpp @@ -0,0 +1,91 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- marks.hpp + +Abstract: +- Definitions for types that are used for "scroll marks" and shell integration + in the buffer. +- Scroll marks are identified by the existence of "ScrollbarData" on a ROW in the buffer. +- Shell integration will then also markup the buffer with special + TextAttributes, to identify regions of text as the Prompt, the Command, the + Output, etc. +- MarkExtents are used to abstract away those regions of text, so a caller + doesn't need to iterate over the buffer themselves. +--*/ + +#pragma once + +enum class MarkCategory : uint8_t +{ + Default = 0, + Error = 1, + Warning = 2, + Success = 3, + Prompt = 4 +}; + +// This is the data that's stored on each ROW, to suggest that there's something +// interesting on this row to show in the scrollbar. Also used in conjunction +// with shell integration - when a prompt is added through shell integration, +// we'll also add a scrollbar mark as a quick "bookmark" to the start of that +// command. +struct ScrollbarData +{ + MarkCategory category{ MarkCategory::Default }; + + // Scrollbar marks may have been given a color, or not. + std::optional color; + + // Prompts without an exit code haven't had a matching FTCS CommandEnd + // called yet. Any value other than 0 is an error. + std::optional exitCode; + // Future consideration: stick the literal command as a string on here, if + // we were given it with the 633;E sequence. +}; + +// Helper struct for describing the bounds of a command and it's output, +// * The Prompt is between the start & end +// * The Command is between the end & commandEnd +// * The Output is between the commandEnd & outputEnd +// +// These are not actually stored in the buffer. The buffer can produce them for +// callers, to make reasoning about regions of the buffer easier. +struct MarkExtents +{ + // Data from the row + ScrollbarData data; + + til::point start; + til::point end; // exclusive + std::optional commandEnd; + std::optional outputEnd; + + // MarkCategory category{ MarkCategory::Info }; + // Other things we may want to think about in the future are listed in + // GH#11000 + + bool HasCommand() const noexcept + { + return commandEnd.has_value() && *commandEnd != end; + } + bool HasOutput() const noexcept + { + return outputEnd.has_value() && *outputEnd != *commandEnd; + } + std::pair GetExtent() const + { + til::point realEnd{ til::coalesce_value(outputEnd, commandEnd, end) }; + return std::make_pair(start, realEnd); + } +}; + +// Another helper, for when callers would like to know just about the data of +// the scrollbar, but don't actually need all the extents of prompts. +struct ScrollMark +{ + til::CoordType row{ 0 }; + ScrollbarData data; +}; diff --git a/src/buffer/out/Row.cpp b/src/buffer/out/Row.cpp index 4722dc0dfc4..8e318da41ef 100644 --- a/src/buffer/out/Row.cpp +++ b/src/buffer/out/Row.cpp @@ -230,6 +230,7 @@ void ROW::Reset(const TextAttribute& attr) noexcept _lineRendition = LineRendition::SingleWidth; _wrapForced = false; _doubleBytePadded = false; + _promptData = std::nullopt; _init(); } @@ -1181,3 +1182,45 @@ CharToColumnMapper ROW::_createCharToColumnMapper(ptrdiff_t offset) const noexce const auto guessedColumn = gsl::narrow_cast(clamp(offset, 0, _columnCount)); return CharToColumnMapper{ _chars.data(), _charOffsets.data(), lastChar, guessedColumn }; } + +const std::optional& ROW::GetScrollbarData() const noexcept +{ + return _promptData; +} +void ROW::SetScrollbarData(std::optional data) noexcept +{ + _promptData = data; +} + +void ROW::StartPrompt() noexcept +{ + if (!_promptData.has_value()) + { + // You'd be tempted to write: + // + // _promptData = ScrollbarData{ + // .category = MarkCategory::Prompt, + // .color = std::nullopt, + // .exitCode = std::nullopt, + // }; + // + // But that's not very optimal! Read this thread for a breakdown of how + // weird std::optional can be some times: + // + // https://github.com/microsoft/terminal/pull/16937#discussion_r1553660833 + + _promptData.emplace(MarkCategory::Prompt); + } +} + +void ROW::EndOutput(std::optional error) noexcept +{ + if (_promptData.has_value()) + { + _promptData->exitCode = error; + if (error.has_value()) + { + _promptData->category = *error == 0 ? MarkCategory::Success : MarkCategory::Error; + } + } +} diff --git a/src/buffer/out/Row.hpp b/src/buffer/out/Row.hpp index 197343df6d8..d26fc87e7ed 100644 --- a/src/buffer/out/Row.hpp +++ b/src/buffer/out/Row.hpp @@ -8,6 +8,7 @@ #include "LineRendition.hpp" #include "OutputCell.hpp" #include "OutputCellIterator.hpp" +#include "Marks.hpp" class ROW; class TextBuffer; @@ -167,6 +168,11 @@ class ROW final auto AttrBegin() const noexcept { return _attr.begin(); } auto AttrEnd() const noexcept { return _attr.end(); } + const std::optional& GetScrollbarData() const noexcept; + void SetScrollbarData(std::optional data) noexcept; + void StartPrompt() noexcept; + void EndOutput(std::optional error) noexcept; + #ifdef UNIT_TESTING friend constexpr bool operator==(const ROW& a, const ROW& b) noexcept; friend class RowTests; @@ -299,6 +305,8 @@ class ROW final bool _wrapForced = false; // Occurs when the user runs out of text to support a double byte character and we're forced to the next line bool _doubleBytePadded = false; + + std::optional _promptData = std::nullopt; }; #ifdef UNIT_TESTING diff --git a/src/buffer/out/TextAttribute.cpp b/src/buffer/out/TextAttribute.cpp index d5b0e5e4a5a..65799d1d6f5 100644 --- a/src/buffer/out/TextAttribute.cpp +++ b/src/buffer/out/TextAttribute.cpp @@ -7,7 +7,7 @@ // Keeping TextColor compact helps us keeping TextAttribute compact, // which in turn ensures that our buffer memory usage is low. -static_assert(sizeof(TextAttribute) == 16); +static_assert(sizeof(TextAttribute) == 18); static_assert(alignof(TextAttribute) == 2); // Ensure that we can memcpy() and memmove() the struct for performance. static_assert(std::is_trivially_copyable_v); @@ -434,4 +434,5 @@ void TextAttribute::SetStandardErase() noexcept { _attrs = CharacterAttributes::Normal; _hyperlinkId = 0; + _markKind = MarkKind::None; } diff --git a/src/buffer/out/TextAttribute.hpp b/src/buffer/out/TextAttribute.hpp index 4fe8c9e637e..44d8563176a 100644 --- a/src/buffer/out/TextAttribute.hpp +++ b/src/buffer/out/TextAttribute.hpp @@ -38,6 +38,15 @@ enum class UnderlineStyle Max = DashedUnderlined }; +// We only need a few bits, but uint8_t apparently doesn't satisfy std::has_unique_object_representations_v +enum class MarkKind : uint16_t +{ + None = 0, + Prompt = 1, + Command = 2, + Output = 3, +}; + class TextAttribute final { public: @@ -46,7 +55,8 @@ class TextAttribute final _foreground{}, _background{}, _hyperlinkId{ 0 }, - _underlineColor{} + _underlineColor{}, + _markKind{ MarkKind::None } { } @@ -55,7 +65,8 @@ class TextAttribute final _foreground{ gsl::at(s_legacyForegroundColorMap, wLegacyAttr & FG_ATTRS) }, _background{ gsl::at(s_legacyBackgroundColorMap, (wLegacyAttr & BG_ATTRS) >> 4) }, _hyperlinkId{ 0 }, - _underlineColor{} + _underlineColor{}, + _markKind{ MarkKind::None } { } @@ -66,7 +77,8 @@ class TextAttribute final _foreground{ rgbForeground }, _background{ rgbBackground }, _hyperlinkId{ 0 }, - _underlineColor{ rgbUnderline } + _underlineColor{ rgbUnderline }, + _markKind{ MarkKind::None } { } @@ -75,7 +87,8 @@ class TextAttribute final _foreground{ foreground }, _background{ background }, _hyperlinkId{ hyperlinkId }, - _underlineColor{ underlineColor } + _underlineColor{ underlineColor }, + _markKind{ MarkKind::None } { } @@ -135,6 +148,15 @@ class TextAttribute final return _attrs; } + constexpr void SetMarkAttributes(const MarkKind attrs) noexcept + { + _markKind = attrs; + } + constexpr MarkKind GetMarkAttributes() const noexcept + { + return _markKind; + } + bool IsHyperlink() const noexcept; TextColor GetForeground() const noexcept; @@ -202,6 +224,7 @@ class TextAttribute final TextColor _foreground; // sizeof: 4, alignof: 1 TextColor _background; // sizeof: 4, alignof: 1 TextColor _underlineColor; // sizeof: 4, alignof: 1 + MarkKind _markKind; // sizeof: 2, alignof: 1 #ifdef UNIT_TESTING friend class TextBufferTests; diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index 75cd6fd4f01..713cf2a09a9 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -1178,7 +1178,6 @@ void TextBuffer::ClearScrollback(const til::CoordType start, const til::CoordTyp GetMutableRowByOffset(y).Reset(_initialAttributes); } - ScrollMarks(-start); ClearMarksInRange(til::point{ 0, height }, til::point{ _width, _height }); } @@ -2375,7 +2374,7 @@ std::string TextBuffer::GenRTF(const CopyRequest& req, // \fsN: specifies font size in half-points. E.g. \fs20 results in a font // size of 10 pts. That's why, font size is multiplied by 2 here. - fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\fs{}"), std::to_string(2 * fontHeightPoints)); + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\fs{}"), 2 * fontHeightPoints); // Set the background color for the page. But the standard way (\cbN) to do // this isn't supported in Word. However, the following control words sequence @@ -2502,8 +2501,7 @@ void TextBuffer::_AppendRTFText(std::string& contentBuilder, const std::wstring_ { // Windows uses unsigned wchar_t - RTF uses signed ones. // '?' is the fallback ascii character. - const auto codeUnitRTFStr = std::to_string(std::bit_cast(codeUnit)); - fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\u{}?"), codeUnitRTFStr); + fmt::format_to(std::back_inserter(contentBuilder), FMT_COMPILE("\\u{}?"), std::bit_cast(codeUnit)); } } } @@ -2750,10 +2748,8 @@ void TextBuffer::Serialize(const wchar_t* destination) const const auto fileSize = gsl::narrow(buffer.size() * sizeof(wchar_t)); DWORD bytesWritten = 0; THROW_IF_WIN32_BOOL_FALSE(WriteFile(file.get(), buffer.data(), fileSize, &bytesWritten, nullptr)); - if (bytesWritten != fileSize) - { - THROW_WIN32_MSG(ERROR_WRITE_FAULT, "failed to write"); - } + THROW_WIN32_IF_MSG(ERROR_WRITE_FAULT, bytesWritten != fileSize, "failed to write"); + buffer.clear(); } if (!moreRowsRemaining) @@ -2872,6 +2868,18 @@ void TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const View oldRowLimit = std::max(oldRowLimit, oldCursorPos.x + 1); } + // Immediately copy this mark over to our new row. The positions of the + // marks themselves will be preserved, since they're just text + // attributes. But the "bookmark" needs to get moved to the new row too. + // * If a row wraps as it reflows, that's fine - we want to leave the + // mark on the row it started on. + // * If the second row of a wrapped row had a mark, and it de-flows onto a + // single row, that's fine! The mark was on that logical row. + if (oldRow.GetScrollbarData().has_value()) + { + newBuffer.GetMutableRowByOffset(newY).SetScrollbarData(oldRow.GetScrollbarData()); + } + til::CoordType oldX = 0; // Copy oldRow into newBuffer until oldRow has been fully consumed. @@ -2991,9 +2999,6 @@ void TextBuffer::Reflow(TextBuffer& oldBuffer, TextBuffer& newBuffer, const View assert(newCursorPos.y >= 0 && newCursorPos.y < newHeight); newCursor.SetSize(oldCursor.GetSize()); newCursor.SetPosition(newCursorPos); - - newBuffer._marks = oldBuffer._marks; - newBuffer._trimMarksOutsideBuffer(); } // Method Description: @@ -3143,9 +3148,82 @@ std::vector TextBuffer::SearchText(const std::wstring_view& nee return results; } -const std::vector& TextBuffer::GetMarks() const noexcept +// Collect up all the rows that were marked, and the data marked on that row. +// This is what should be used for hot paths, like updating the scrollbar. +std::vector TextBuffer::GetMarkRows() const { - return _marks; + std::vector marks; + const auto bottom = _estimateOffsetOfLastCommittedRow(); + for (auto y = 0; y <= bottom; y++) + { + const auto& row = GetRowByOffset(y); + const auto& data{ row.GetScrollbarData() }; + if (data.has_value()) + { + marks.emplace_back(y, *data); + } + } + return marks; +} + +// Get all the regions for all the shell integration marks in the buffer. +// Marks will be returned in top-down order. +// +// This possibly iterates over every run in the buffer, so don't do this on a +// hot path. Just do this once per user input, if at all possible. +// +// Use `limit` to control how many you get, _starting from the bottom_. (e.g. +// limit=1 will just give you the "most recent mark"). +std::vector TextBuffer::GetMarkExtents(size_t limit) const +{ + if (limit == 0u) + { + return {}; + } + + std::vector marks{}; + const auto bottom = _estimateOffsetOfLastCommittedRow(); + auto lastPromptY = bottom; + for (auto promptY = bottom; promptY >= 0; promptY--) + { + const auto& currRow = GetRowByOffset(promptY); + auto& rowPromptData = currRow.GetScrollbarData(); + if (!rowPromptData.has_value()) + { + // This row didn't start a prompt, don't even look here. + continue; + } + + // Future thought! In #11000 & #14792, we considered the possibility of + // scrolling to only an error mark, or something like that. Perhaps in + // the future, add a customizable filter that's a set of types of mark + // to include? + // + // For now, skip any "Default" marks, since those came from the UI. We + // just want the ones that correspond to shell integration. + + if (rowPromptData->category == MarkCategory::Default) + { + continue; + } + + // This row did start a prompt! Find the prompt that starts here. + // Presumably, no rows below us will have prompts, so pass in the last + // row with text as the bottom + marks.push_back(_scrollMarkExtentForRow(promptY, lastPromptY)); + + // operator>=(T, optional) will return true if the optional is + // nullopt, unfortunately. + if (marks.size() >= limit) + { + break; + } + + lastPromptY = promptY; + } + + std::reverse(marks.begin(), marks.end()); + return marks; } // Remove all marks between `start` & `end`, inclusive. @@ -3153,115 +3231,318 @@ void TextBuffer::ClearMarksInRange( const til::point start, const til::point end) { - auto inRange = [&start, &end](const ScrollMark& m) { - return (m.start >= start && m.start <= end) || - (m.end >= start && m.end <= end); - }; + auto top = std::clamp(std::min(start.y, end.y), 0, _height - 1); + auto bottom = std::clamp(std::max(start.y, end.y), 0, _estimateOffsetOfLastCommittedRow()); - _marks.erase(std::remove_if(_marks.begin(), - _marks.end(), - inRange), - _marks.end()); + for (auto y = top; y <= bottom; y++) + { + auto& row = GetMutableRowByOffset(y); + auto& runs = row.Attributes().runs(); + row.SetScrollbarData(std::nullopt); + for (auto& [attr, length] : runs) + { + attr.SetMarkAttributes(MarkKind::None); + } + } } -void TextBuffer::ClearAllMarks() noexcept +void TextBuffer::ClearAllMarks() { - _marks.clear(); + ClearMarksInRange({ 0, 0 }, { _width - 1, _height - 1 }); } -// Adjust all the marks in the y-direction by `delta`. Positive values move the -// marks down (the positive y direction). Negative values move up. This will -// trim marks that are no longer have a start in the bounds of the buffer -void TextBuffer::ScrollMarks(const int delta) +// Collect up the extent of the prompt and possibly command and output for the +// mark that starts on this row. +MarkExtents TextBuffer::_scrollMarkExtentForRow(const til::CoordType rowOffset, + const til::CoordType bottomInclusive) const { - for (auto& mark : _marks) - { - mark.start.y += delta; + const auto& startRow = GetRowByOffset(rowOffset); + const auto& rowPromptData = startRow.GetScrollbarData(); + assert(rowPromptData.has_value()); + + MarkExtents mark{ + .data = *rowPromptData, + }; + + bool startedPrompt = false; + bool startedCommand = false; + bool startedOutput = false; + MarkKind lastMarkKind = MarkKind::Output; - // If the mark had sub-regions, then move those pointers too - if (mark.commandEnd.has_value()) + const auto endThisMark = [&](auto x, auto y) { + if (startedOutput) { - (*mark.commandEnd).y += delta; + mark.outputEnd = til::point{ x, y }; } - if (mark.outputEnd.has_value()) + if (!startedOutput && startedCommand) { - (*mark.outputEnd).y += delta; + mark.commandEnd = til::point{ x, y }; } + if (!startedCommand) + { + mark.end = til::point{ x, y }; + } + }; + auto x = 0; + auto y = rowOffset; + til::point lastMarkedText{ x, y }; + for (; y <= bottomInclusive; y++) + { + // Now we need to iterate over text attributes. We need to find a + // segment of Prompt attributes, we'll skip those. Then there should be + // Command attributes. Collect up all of those, till we get to the next + // Output attribute. + + const auto& row = GetRowByOffset(y); + const auto runs = row.Attributes().runs(); + x = 0; + for (const auto& [attr, length] : runs) + { + const auto nextX = gsl::narrow_cast(x + length); + const auto markKind{ attr.GetMarkAttributes() }; + + if (markKind != MarkKind::None) + { + lastMarkedText = { nextX, y }; + + if (markKind == MarkKind::Prompt) + { + if (startedCommand || startedOutput) + { + // we got a _new_ prompt. bail out. + break; + } + if (!startedPrompt) + { + // We entered the first prompt here + startedPrompt = true; + mark.start = til::point{ x, y }; + } + endThisMark(lastMarkedText.x, lastMarkedText.y); + } + else if (markKind == MarkKind::Command && startedPrompt) + { + startedCommand = true; + endThisMark(lastMarkedText.x, lastMarkedText.y); + } + else if ((markKind == MarkKind::Output) && startedPrompt) + { + startedOutput = true; + if (!mark.commandEnd.has_value()) + { + // immediately just end the command at the start here, so we can treat this whole run as output + mark.commandEnd = mark.end; + startedCommand = true; + } + + endThisMark(lastMarkedText.x, lastMarkedText.y); + } + // Otherwise, we've changed from any state -> any state, and it doesn't really matter. + lastMarkKind = markKind; + } + // advance to next run of text + x = nextX; + } + // we went over all the runs in this row, but we're not done yet. Keep iterating on the next row. } - _trimMarksOutsideBuffer(); -} -// Method Description: -// - Add a mark to our list of marks, and treat it as the active "prompt". For -// the sake of shell integration, we need to know which mark represents the -// current prompt/command/output. Internally, we'll always treat the _last_ -// mark in the list as the current prompt. -// Arguments: -// - m: the mark to add. -void TextBuffer::StartPromptMark(const ScrollMark& m) -{ - _marks.push_back(m); + // Okay, we're at the bottom of the buffer? Yea, just return what we found. + if (!startedCommand) + { + // If we never got to a Command or Output run, then we never set .end. + // Set it here to the last run we saw. + endThisMark(lastMarkedText.x, lastMarkedText.y); + } + return mark; } -// Method Description: -// - Add a mark to our list of marks. Don't treat this as the active prompt. -// This should be used for marks created by the UI or from other user input. -// By inserting at the start of the list, we can separate out marks that were -// generated by client programs vs ones created by the user. -// Arguments: -// - m: the mark to add. -void TextBuffer::AddMark(const ScrollMark& m) + +std::wstring TextBuffer::_commandForRow(const til::CoordType rowOffset, const til::CoordType bottomInclusive) const { - _marks.insert(_marks.begin(), m); + std::wstring commandBuilder; + MarkKind lastMarkKind = MarkKind::Prompt; + for (auto y = rowOffset; y <= bottomInclusive; y++) + { + // Now we need to iterate over text attributes. We need to find a + // segment of Prompt attributes, we'll skip those. Then there should be + // Command attributes. Collect up all of those, till we get to the next + // Output attribute. + + const auto& row = GetRowByOffset(y); + const auto runs = row.Attributes().runs(); + auto x = 0; + for (const auto& [attr, length] : runs) + { + const auto nextX = gsl::narrow_cast(x + length); + const auto markKind{ attr.GetMarkAttributes() }; + if (markKind != lastMarkKind) + { + if (lastMarkKind == MarkKind::Command) + { + // We've changed away from being in a command. We're done. + // Return what we've gotten so far. + return commandBuilder; + } + // Otherwise, we've changed from any state -> any state, and it doesn't really matter. + lastMarkKind = markKind; + } + + if (markKind == MarkKind::Command) + { + commandBuilder += row.GetText(x, nextX); + } + // advance to next run of text + x = nextX; + } + // we went over all the runs in this row, but we're not done yet. Keep iterating on the next row. + } + // Okay, we're at the bottom of the buffer? Yea, just return what we found. + return commandBuilder; } -void TextBuffer::_trimMarksOutsideBuffer() +std::wstring TextBuffer::CurrentCommand() const { - const til::CoordType height = _height; - std::erase_if(_marks, [height](const auto& m) { - return (m.start.y < 0) || (m.start.y >= height); - }); + auto promptY = GetCursor().GetPosition().y; + for (; promptY >= 0; promptY--) + { + const auto& currRow = GetRowByOffset(promptY); + auto& rowPromptData = currRow.GetScrollbarData(); + if (!rowPromptData.has_value()) + { + // This row didn't start a prompt, don't even look here. + continue; + } + + // This row did start a prompt! Find the prompt that starts here. + // Presumably, no rows below us will have prompts, so pass in the last + // row with text as the bottom + return _commandForRow(promptY, _estimateOffsetOfLastCommittedRow()); + } + return L""; } -std::wstring_view TextBuffer::CurrentCommand() const +std::vector TextBuffer::Commands() const { - if (_marks.size() == 0) + std::vector commands{}; + const auto bottom = _estimateOffsetOfLastCommittedRow(); + auto lastPromptY = bottom; + for (auto promptY = bottom; promptY >= 0; promptY--) { - return L""; + const auto& currRow = GetRowByOffset(promptY); + auto& rowPromptData = currRow.GetScrollbarData(); + if (!rowPromptData.has_value()) + { + // This row didn't start a prompt, don't even look here. + continue; + } + + // This row did start a prompt! Find the prompt that starts here. + // Presumably, no rows below us will have prompts, so pass in the last + // row with text as the bottom + auto foundCommand = _commandForRow(promptY, lastPromptY); + if (!foundCommand.empty()) + { + commands.emplace_back(std::move(foundCommand)); + } + lastPromptY = promptY; } + std::reverse(commands.begin(), commands.end()); + return commands; +} - const auto& curr{ _marks.back() }; - const auto& start{ curr.end }; - const auto& end{ GetCursor().GetPosition() }; +void TextBuffer::StartPrompt() +{ + const auto currentRowOffset = GetCursor().GetPosition().y; + auto& currentRow = GetMutableRowByOffset(currentRowOffset); + currentRow.StartPrompt(); - const auto line = start.y; - const auto& row = GetRowByOffset(line); - return row.GetText(start.x, end.x); + _currentAttributes.SetMarkAttributes(MarkKind::Prompt); } -void TextBuffer::SetCurrentPromptEnd(const til::point pos) noexcept +bool TextBuffer::_createPromptMarkIfNeeded() { - if (_marks.empty()) + // We might get here out-of-order, without seeing a StartPrompt (FTCS A) + // first. Since StartPrompt actually sets up the prompt mark on the ROW, we + // need to do a bit of extra work here to start a new mark (if the last one + // wasn't in an appropriate state). + + const auto mostRecentMarks = GetMarkExtents(1u); + if (!mostRecentMarks.empty()) { - return; + const auto& mostRecentMark = til::at(mostRecentMarks, 0); + if (!mostRecentMark.HasOutput()) + { + // The most recent command mark _didn't_ have output yet. Great! + // we'll leave it alone, and just start treating text as Command or Output. + return false; + } + + // The most recent command mark had output. That suggests that either: + // * shell integration wasn't enabled (but the user would still + // like lines with enters to be marked as prompts) + // * or we're in the middle of a command that's ongoing. + + // If it does have a command, then we're still in the output of + // that command. + // --> the current attrs should already be set to Output. + if (mostRecentMark.HasCommand()) + { + return false; + } + // If the mark doesn't have any command - then we know we're + // playing silly games with just marking whole lines as prompts, + // then immediately going to output. + // --> Below, we'll add a new mark to this row. } - auto& curr{ _marks.back() }; - curr.end = pos; + + // There were no marks at all! + // --> add a new mark to this row, set all the attrs in this row + // to be Prompt, and set the current attrs to Output. + + auto& row = GetMutableRowByOffset(GetCursor().GetPosition().y); + row.StartPrompt(); + return true; +} + +bool TextBuffer::StartCommand() +{ + const auto createdMark = _createPromptMarkIfNeeded(); + _currentAttributes.SetMarkAttributes(MarkKind::Command); + return createdMark; +} +bool TextBuffer::StartOutput() +{ + const auto createdMark = _createPromptMarkIfNeeded(); + _currentAttributes.SetMarkAttributes(MarkKind::Output); + return createdMark; } -void TextBuffer::SetCurrentCommandEnd(const til::point pos) noexcept + +// Find the row above the cursor where this most recent prompt started, and set +// the exit code on that row's scroll mark. +void TextBuffer::EndCurrentCommand(std::optional error) { - if (_marks.empty()) + _currentAttributes.SetMarkAttributes(MarkKind::None); + + for (auto y = GetCursor().GetPosition().y; y >= 0; y--) { - return; + auto& currRow = GetMutableRowByOffset(y); + auto& rowPromptData = currRow.GetScrollbarData(); + if (rowPromptData.has_value()) + { + currRow.EndOutput(error); + return; + } } - auto& curr{ _marks.back() }; - curr.commandEnd = pos; } -void TextBuffer::SetCurrentOutputEnd(const til::point pos, ::MarkCategory category) noexcept + +void TextBuffer::SetScrollbarData(ScrollbarData mark, til::CoordType y) +{ + auto& row = GetMutableRowByOffset(y); + row.SetScrollbarData(mark); +} +void TextBuffer::ManuallyMarkRowAsPrompt(til::CoordType y) { - if (_marks.empty()) + auto& row = GetMutableRowByOffset(y); + for (auto& [attr, len] : row.Attributes().runs()) { - return; + attr.SetMarkAttributes(MarkKind::Prompt); } - auto& curr{ _marks.back() }; - curr.outputEnd = pos; - curr.category = category; } diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp index 8093aea9af8..79872cea0c4 100644 --- a/src/buffer/out/textBuffer.hpp +++ b/src/buffer/out/textBuffer.hpp @@ -66,41 +66,6 @@ namespace Microsoft::Console::Render class Renderer; } -enum class MarkCategory -{ - Prompt = 0, - Error = 1, - Warning = 2, - Success = 3, - Info = 4 -}; -struct ScrollMark -{ - std::optional color; - til::point start; - til::point end; // exclusive - std::optional commandEnd; - std::optional outputEnd; - - MarkCategory category{ MarkCategory::Info }; - // Other things we may want to think about in the future are listed in - // GH#11000 - - bool HasCommand() const noexcept - { - return commandEnd.has_value() && *commandEnd != end; - } - bool HasOutput() const noexcept - { - return outputEnd.has_value() && *outputEnd != *commandEnd; - } - std::pair GetExtent() const - { - til::point realEnd{ til::coalesce_value(outputEnd, commandEnd, end) }; - return std::make_pair(til::point{ start }, realEnd); - } -}; - class TextBuffer final { public: @@ -332,16 +297,19 @@ class TextBuffer final std::vector SearchText(const std::wstring_view& needle, bool caseInsensitive) const; std::vector SearchText(const std::wstring_view& needle, bool caseInsensitive, til::CoordType rowBeg, til::CoordType rowEnd) const; - const std::vector& GetMarks() const noexcept; + // Mark handling + std::vector GetMarkRows() const; + std::vector GetMarkExtents(size_t limit = SIZE_T_MAX) const; void ClearMarksInRange(const til::point start, const til::point end); - void ClearAllMarks() noexcept; - void ScrollMarks(const int delta); - void StartPromptMark(const ScrollMark& m); - void AddMark(const ScrollMark& m); - void SetCurrentPromptEnd(const til::point pos) noexcept; - void SetCurrentCommandEnd(const til::point pos) noexcept; - void SetCurrentOutputEnd(const til::point pos, ::MarkCategory category) noexcept; - std::wstring_view CurrentCommand() const; + void ClearAllMarks(); + std::wstring CurrentCommand() const; + std::vector Commands() const; + void StartPrompt(); + bool StartCommand(); + bool StartOutput(); + void EndCurrentCommand(std::optional error); + void SetScrollbarData(ScrollbarData mark, til::CoordType y); + void ManuallyMarkRowAsPrompt(til::CoordType y); private: void _reserve(til::size screenBufferSize, const TextAttribute& defaultAttributes); @@ -366,7 +334,11 @@ class TextBuffer final til::point _GetWordEndForAccessibility(const til::point target, const std::wstring_view wordDelimiters, const til::point limit) const; til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const; void _PruneHyperlinks(); - void _trimMarksOutsideBuffer(); + + std::wstring _commandForRow(const til::CoordType rowOffset, const til::CoordType bottomInclusive) const; + MarkExtents _scrollMarkExtentForRow(const til::CoordType rowOffset, const til::CoordType bottomInclusive) const; + bool _createPromptMarkIfNeeded(); + std::tuple _RowCopyHelper(const CopyRequest& req, const til::CoordType iRow, const ROW& row) const; static void _AppendRTFText(std::string& contentBuilder, const std::wstring_view& text); @@ -439,7 +411,6 @@ class TextBuffer final uint64_t _lastMutationId = 0; Cursor _cursor; - std::vector _marks; bool _isActiveBuffer = false; #ifdef UNIT_TESTING diff --git a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp index 3d0837d0a64..f3c0efa463d 100644 --- a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp +++ b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp @@ -381,14 +381,15 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - auto myCommand = myArgs.TerminalArgs().Commandline(); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + auto myCommand = terminalArgs.Commandline(); VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg \"", myCommand); } { @@ -397,14 +398,15 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - auto myCommand = myArgs.TerminalArgs().Commandline(); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + auto myCommand = terminalArgs.Commandline(); VERIFY_ARE_EQUAL(L"\" with spaces\"", myCommand); } } @@ -421,14 +423,15 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - auto myCommand = myArgs.TerminalArgs().Commandline(); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + auto myCommand = terminalArgs.Commandline(); VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg ; with spaces\"", myCommand); } } @@ -468,14 +471,15 @@ namespace TerminalAppLocalTests 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_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -489,15 +493,16 @@ namespace TerminalAppLocalTests 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_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"cmd", myArgs.TerminalArgs().Profile()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"cmd", terminalArgs.Profile()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -511,15 +516,16 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"c:\\Foo", myArgs.TerminalArgs().StartingDirectory()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_FALSE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"c:\\Foo", terminalArgs.StartingDirectory()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -533,15 +539,16 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"powershell.exe", myArgs.TerminalArgs().Commandline()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"powershell.exe", terminalArgs.Commandline()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -555,16 +562,17 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - auto myCommand = myArgs.TerminalArgs().Commandline(); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + auto myCommand = terminalArgs.Commandline(); VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\"", myCommand); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -578,16 +586,17 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - auto myCommand = myArgs.TerminalArgs().Commandline(); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + auto myCommand = terminalArgs.Commandline(); VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\" another-arg \"more spaces in this one\"", myCommand); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -601,15 +610,16 @@ namespace TerminalAppLocalTests 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_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"Windows PowerShell", myArgs.TerminalArgs().Profile()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"Windows PowerShell", terminalArgs.Profile()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -623,14 +633,15 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"wsl -d Alpine", terminalArgs.Commandline()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -644,16 +655,17 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline()); - VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"wsl -d Alpine", terminalArgs.Commandline()); + VERIFY_ARE_EQUAL(L"1", terminalArgs.Profile()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -669,15 +681,16 @@ namespace TerminalAppLocalTests 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_NOT_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_ARE_EQUAL(til::color(myArgs.TerminalArgs().TabColor().Value()), expectedColor); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NOT_NULL(terminalArgs.TabColor()); + VERIFY_ARE_EQUAL(til::color(terminalArgs.TabColor().Value()), expectedColor); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -693,15 +706,16 @@ namespace TerminalAppLocalTests 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_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().ColorScheme().empty()); - VERIFY_ARE_EQUAL(expectedScheme, myArgs.TerminalArgs().ColorScheme()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_IS_FALSE(terminalArgs.ColorScheme().empty()); + VERIFY_ARE_EQUAL(expectedScheme, terminalArgs.ColorScheme()); } } @@ -732,7 +746,8 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection()); VERIFY_ARE_EQUAL(SplitType::Manual, myArgs.SplitMode()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); } { AppCommandlineArgs appArgs{}; @@ -752,7 +767,8 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Down, myArgs.SplitDirection()); VERIFY_ARE_EQUAL(SplitType::Manual, myArgs.SplitMode()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); } { AppCommandlineArgs appArgs{}; @@ -774,7 +790,8 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Right, myArgs.SplitDirection()); VERIFY_ARE_EQUAL(SplitType::Manual, myArgs.SplitMode()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); } { AppCommandlineArgs appArgs{}; @@ -795,7 +812,8 @@ namespace TerminalAppLocalTests auto myArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitType::Duplicate, myArgs.SplitMode()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); } { AppCommandlineArgs appArgs{}; @@ -815,16 +833,17 @@ namespace TerminalAppLocalTests auto myArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline()); - VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"wsl -d Alpine", terminalArgs.Commandline()); + VERIFY_ARE_EQUAL(L"1", terminalArgs.Profile()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -844,16 +863,17 @@ namespace TerminalAppLocalTests auto myArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Down, myArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline()); - VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"wsl -d Alpine", terminalArgs.Commandline()); + VERIFY_ARE_EQUAL(L"1", terminalArgs.Profile()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } { AppCommandlineArgs appArgs{}; @@ -873,16 +893,17 @@ namespace TerminalAppLocalTests auto myArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"wsl -d Alpine -H", myArgs.TerminalArgs().Commandline()); - VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().ColorScheme().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"wsl -d Alpine -H", terminalArgs.Commandline()); + VERIFY_ARE_EQUAL(L"1", terminalArgs.Profile()); + VERIFY_IS_TRUE(terminalArgs.ColorScheme().empty()); } } @@ -923,13 +944,14 @@ namespace TerminalAppLocalTests 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_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); } { AppCommandlineArgs appArgs{}; @@ -943,14 +965,15 @@ namespace TerminalAppLocalTests 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_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"cmd", myArgs.TerminalArgs().Profile()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"cmd", terminalArgs.Profile()); } { AppCommandlineArgs appArgs{}; @@ -964,14 +987,15 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"c:\\Foo", myArgs.TerminalArgs().StartingDirectory()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_FALSE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"c:\\Foo", terminalArgs.StartingDirectory()); } { AppCommandlineArgs appArgs{}; @@ -985,14 +1009,15 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"powershell.exe", myArgs.TerminalArgs().Commandline()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"powershell.exe", terminalArgs.Commandline()); } { AppCommandlineArgs appArgs{}; @@ -1006,14 +1031,15 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\"", myArgs.TerminalArgs().Commandline()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\"", terminalArgs.Commandline()); } } @@ -1469,32 +1495,38 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size()); - auto actionAndArgs = appArgs._startupActions.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_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - - actionAndArgs = appArgs._startupActions.at(1); - VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); - VERIFY_IS_NOT_NULL(actionAndArgs.Args()); - myArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(myArgs); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); - VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"slpit-pane", myArgs.TerminalArgs().Commandline()); + { + auto actionAndArgs = appArgs._startupActions.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); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + } + + { + auto actionAndArgs = appArgs._startupActions.at(1); + VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); + VERIFY_IS_NOT_NULL(actionAndArgs.Args()); + auto myArgs = actionAndArgs.Args().try_as(); + VERIFY_IS_NOT_NULL(myArgs); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"slpit-pane", terminalArgs.Commandline()); + } } { @@ -1511,8 +1543,9 @@ namespace TerminalAppLocalTests auto myArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(myArgs); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); - VERIFY_ARE_EQUAL(L"slpit-pane -H", myArgs.TerminalArgs().Commandline()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_ARE_EQUAL(L"slpit-pane -H", terminalArgs.Commandline()); } } @@ -1530,9 +1563,10 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(actionAndArgs.Args()); auto myArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(myArgs); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); - VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline()); - VERIFY_ARE_EQUAL(L"C:\\", myArgs.TerminalArgs().StartingDirectory()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_ARE_EQUAL(L"wsl -d Alpine", terminalArgs.Commandline()); + VERIFY_ARE_EQUAL(L"C:\\", terminalArgs.StartingDirectory()); } { // two parsing terminators, new-tab command AppCommandlineArgs appArgs{}; @@ -1546,9 +1580,10 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(actionAndArgs.Args()); auto myArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(myArgs); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); - VERIFY_ARE_EQUAL(L"wsl -d Alpine -- sleep 10", myArgs.TerminalArgs().Commandline()); - VERIFY_ARE_EQUAL(L"C:\\", myArgs.TerminalArgs().StartingDirectory()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_ARE_EQUAL(L"wsl -d Alpine -- sleep 10", terminalArgs.Commandline()); + VERIFY_ARE_EQUAL(L"C:\\", terminalArgs.StartingDirectory()); } { // two parsing terminators, *no* command AppCommandlineArgs appArgs{}; @@ -1562,9 +1597,10 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(actionAndArgs.Args()); auto myArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(myArgs); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); - VERIFY_ARE_EQUAL(L"wsl -d Alpine -- sleep 10", myArgs.TerminalArgs().Commandline()); - VERIFY_ARE_EQUAL(L"C:\\", myArgs.TerminalArgs().StartingDirectory()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_ARE_EQUAL(L"wsl -d Alpine -- sleep 10", terminalArgs.Commandline()); + VERIFY_ARE_EQUAL(L"C:\\", terminalArgs.StartingDirectory()); } } @@ -1578,13 +1614,14 @@ namespace TerminalAppLocalTests 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_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); } void CommandlineTest::TestMultipleCommandExecuteCommandlineAction() @@ -1598,13 +1635,14 @@ namespace TerminalAppLocalTests 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_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); } { auto actionAndArgs = actions.at(1); @@ -1612,13 +1650,14 @@ namespace TerminalAppLocalTests 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_NULL(myArgs.TerminalArgs().TabColor()); - VERIFY_IS_NULL(myArgs.TerminalArgs().ProfileIndex()); - VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_NULL(terminalArgs.TabColor()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); } } @@ -1739,13 +1778,14 @@ namespace TerminalAppLocalTests 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_FALSE(myArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"cmd", myArgs.TerminalArgs().Profile()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.ProfileIndex() == nullptr); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"cmd", terminalArgs.Profile()); } { Log::Comment(NoThrowString().Format(L"Pass a launch mode and command line")); @@ -1763,13 +1803,14 @@ namespace TerminalAppLocalTests 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_FALSE(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()); - VERIFY_ARE_EQUAL(L"powershell.exe", myArgs.TerminalArgs().Commandline()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.ProfileIndex() == nullptr); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"powershell.exe", terminalArgs.Commandline()); } } @@ -1800,7 +1841,8 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection()); VERIFY_ARE_EQUAL(0.5f, myArgs.SplitSize()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); } { AppCommandlineArgs appArgs{}; @@ -1820,7 +1862,8 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection()); VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); } { AppCommandlineArgs appArgs{}; @@ -1841,7 +1884,8 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection()); VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); } { auto actionAndArgs = appArgs._startupActions.at(2); @@ -1851,7 +1895,8 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection()); VERIFY_ARE_EQUAL(0.5f, myArgs.SplitSize()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); } } { @@ -1873,7 +1918,8 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection()); VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); } { auto actionAndArgs = appArgs._startupActions.at(2); @@ -1883,7 +1929,8 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(myArgs); VERIFY_ARE_EQUAL(SplitDirection::Automatic, myArgs.SplitDirection()); VERIFY_ARE_EQUAL(0.7f, myArgs.SplitSize()); - VERIFY_IS_NOT_NULL(myArgs.TerminalArgs()); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); } } } diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index 8116e9e4574..ead353fc900 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -176,12 +176,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"${profile.name}", terminalArgs.Profile()); } const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() }; @@ -201,12 +202,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile0", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile0", terminalArgs.Profile()); } { @@ -220,12 +222,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", terminalArgs.Profile()); } { @@ -239,12 +242,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile2", terminalArgs.Profile()); } } @@ -302,12 +306,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"${profile.name}", terminalArgs.Profile()); } const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() }; @@ -328,12 +333,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile0", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile0", terminalArgs.Profile()); } { @@ -348,12 +354,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", terminalArgs.Profile()); } { @@ -368,12 +375,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile2", terminalArgs.Profile()); } } @@ -433,12 +441,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"${profile.name}", terminalArgs.Profile()); } const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() }; @@ -459,12 +468,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile0", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile0", terminalArgs.Profile()); } { @@ -479,12 +489,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile1\"", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1\"", terminalArgs.Profile()); } { @@ -499,12 +510,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile2", terminalArgs.Profile()); } } @@ -692,12 +704,13 @@ namespace TerminalAppLocalTests const auto& realArgs = childActionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"ssh me@first.com", realArgs.TerminalArgs().Commandline()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"ssh me@first.com", terminalArgs.Commandline()); VERIFY_IS_FALSE(child.HasNestedCommands()); } @@ -712,12 +725,13 @@ namespace TerminalAppLocalTests const auto& realArgs = childActionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"ssh me@second.com", realArgs.TerminalArgs().Commandline()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"ssh me@second.com", terminalArgs.Commandline()); VERIFY_IS_FALSE(child.HasNestedCommands()); } @@ -818,12 +832,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(name, realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(name, terminalArgs.Profile()); VERIFY_IS_FALSE(childCommand.HasNestedCommands()); } @@ -839,12 +854,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(name, realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(name, terminalArgs.Profile()); VERIFY_IS_FALSE(childCommand.HasNestedCommands()); } @@ -860,12 +876,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(name, realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(name, terminalArgs.Profile()); VERIFY_IS_FALSE(childCommand.HasNestedCommands()); } @@ -951,12 +968,13 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(name, realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(name, terminalArgs.Profile()); VERIFY_IS_FALSE(command.HasNestedCommands()); } @@ -1069,12 +1087,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(name, realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(name, terminalArgs.Profile()); VERIFY_IS_FALSE(childCommand.HasNestedCommands()); } @@ -1090,12 +1109,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(name, realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(name, terminalArgs.Profile()); VERIFY_IS_FALSE(childCommand.HasNestedCommands()); } @@ -1111,12 +1131,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(name, realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(name, terminalArgs.Profile()); VERIFY_IS_FALSE(childCommand.HasNestedCommands()); } @@ -1245,12 +1266,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"${scheme.name}", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"${scheme.name}", terminalArgs.Profile()); } const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() }; @@ -1274,12 +1296,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"Campbell", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"Campbell", terminalArgs.Profile()); } { @@ -1294,12 +1317,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"Campbell PowerShell", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"Campbell PowerShell", terminalArgs.Profile()); } { @@ -1314,12 +1338,13 @@ namespace TerminalAppLocalTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Automatic, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"Vintage", realArgs.TerminalArgs().Profile()); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"Vintage", terminalArgs.Profile()); } } @@ -1385,15 +1410,16 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile0", realArgs.TerminalArgs().Profile()); - VERIFY_IS_NULL(realArgs.TerminalArgs().Elevate()); - - const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile0", terminalArgs.Profile()); + VERIFY_IS_NULL(terminalArgs.Elevate()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, terminalArgs, nullptr); const auto termSettings = termSettingsResult.DefaultSettings(); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(false, termSettings.Elevate()); @@ -1407,15 +1433,16 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); - VERIFY_IS_NULL(realArgs.TerminalArgs().Elevate()); - - const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", terminalArgs.Profile()); + VERIFY_IS_NULL(terminalArgs.Elevate()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, terminalArgs, nullptr); const auto termSettings = termSettingsResult.DefaultSettings(); VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(true, termSettings.Elevate()); @@ -1429,15 +1456,16 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - VERIFY_IS_NULL(realArgs.TerminalArgs().Elevate()); - - const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile2", terminalArgs.Profile()); + VERIFY_IS_NULL(terminalArgs.Elevate()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, terminalArgs, nullptr); const auto termSettings = termSettingsResult.DefaultSettings(); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(false, termSettings.Elevate()); @@ -1452,16 +1480,17 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile0", realArgs.TerminalArgs().Profile()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Elevate().Value()); - - const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile0", terminalArgs.Profile()); + VERIFY_IS_NOT_NULL(terminalArgs.Elevate()); + VERIFY_IS_FALSE(terminalArgs.Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, terminalArgs, nullptr); const auto termSettings = termSettingsResult.DefaultSettings(); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(false, termSettings.Elevate()); @@ -1475,16 +1504,17 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Elevate().Value()); - - const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", terminalArgs.Profile()); + VERIFY_IS_NOT_NULL(terminalArgs.Elevate()); + VERIFY_IS_FALSE(terminalArgs.Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, terminalArgs, nullptr); const auto termSettings = termSettingsResult.DefaultSettings(); VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(false, termSettings.Elevate()); @@ -1498,16 +1528,17 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Elevate().Value()); - - const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile2", terminalArgs.Profile()); + VERIFY_IS_NOT_NULL(terminalArgs.Elevate()); + VERIFY_IS_FALSE(terminalArgs.Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, terminalArgs, nullptr); const auto termSettings = termSettingsResult.DefaultSettings(); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(false, termSettings.Elevate()); @@ -1522,16 +1553,17 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile0", realArgs.TerminalArgs().Profile()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Elevate().Value()); - - const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile0", terminalArgs.Profile()); + VERIFY_IS_NOT_NULL(terminalArgs.Elevate()); + VERIFY_IS_TRUE(terminalArgs.Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, terminalArgs, nullptr); const auto termSettings = termSettingsResult.DefaultSettings(); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(true, termSettings.Elevate()); @@ -1544,16 +1576,17 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Elevate().Value()); - - const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", terminalArgs.Profile()); + VERIFY_IS_NOT_NULL(terminalArgs.Elevate()); + VERIFY_IS_TRUE(terminalArgs.Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, terminalArgs, nullptr); const auto termSettings = termSettingsResult.DefaultSettings(); VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(true, termSettings.Elevate()); @@ -1567,16 +1600,17 @@ namespace TerminalAppLocalTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().Elevate()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Elevate().Value()); - - const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, realArgs.TerminalArgs(), nullptr); + auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile2", terminalArgs.Profile()); + VERIFY_IS_NOT_NULL(terminalArgs.Elevate()); + VERIFY_IS_TRUE(terminalArgs.Elevate().Value()); + + const auto termSettingsResult = TerminalSettings::CreateWithNewTerminalArgs(settings, terminalArgs, nullptr); const auto termSettings = termSettingsResult.DefaultSettings(); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); VERIFY_ARE_EQUAL(true, termSettings.Elevate()); diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 22a0582dfda..2fd6680574f 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -238,6 +238,32 @@ namespace winrt::TerminalApp::implementation } } + // * Helper to try and get a ProfileIndex out of a NewTerminalArgs out of a + // NewContentArgs. For the new tab and split pane action, we want to _not_ + // handle the event if an invalid profile index was passed. + // + // Return value: + // * True if the args are NewTerminalArgs, and the profile index was out of bounds. + // * False otherwise. + static bool _shouldBailForInvalidProfileIndex(const CascadiaSettings& settings, const INewContentArgs& args) + { + if (!args) + { + return false; + } + if (const auto& terminalArgs{ args.try_as() }) + { + if (const auto index = terminalArgs.ProfileIndex()) + { + if (gsl::narrow(index.Value()) >= settings.ActiveProfiles().Size()) + { + return true; + } + } + } + return false; + } + void TerminalPage::_HandleSplitPane(const IInspectable& sender, const ActionEventArgs& args) { @@ -247,16 +273,10 @@ namespace winrt::TerminalApp::implementation } else if (const auto& realArgs = args.ActionArgs().try_as()) { - if (const auto& newTerminalArgs{ realArgs.TerminalArgs() }) + if (_shouldBailForInvalidProfileIndex(_settings, realArgs.ContentArgs())) { - if (const auto index = realArgs.TerminalArgs().ProfileIndex()) - { - if (gsl::narrow(index.Value()) >= _settings.ActiveProfiles().Size()) - { - args.Handled(false); - return; - } - } + args.Handled(false); + return; } const auto& duplicateFromTab{ realArgs.SplitMode() == SplitType::Duplicate ? _GetFocusedTab() : nullptr }; @@ -267,7 +287,7 @@ namespace winrt::TerminalApp::implementation realArgs.SplitDirection(), // This is safe, we're already filtering so the value is (0, 1) ::base::saturated_cast(realArgs.SplitSize()), - _MakePane(realArgs.TerminalArgs(), duplicateFromTab)); + _MakePane(realArgs.ContentArgs(), duplicateFromTab)); args.Handled(true); } } @@ -445,19 +465,13 @@ namespace winrt::TerminalApp::implementation } else if (const auto& realArgs = args.ActionArgs().try_as()) { - if (const auto& newTerminalArgs{ realArgs.TerminalArgs() }) + if (_shouldBailForInvalidProfileIndex(_settings, realArgs.ContentArgs())) { - if (const auto index = newTerminalArgs.ProfileIndex()) - { - if (gsl::narrow(index.Value()) >= _settings.ActiveProfiles().Size()) - { - args.Handled(false); - return; - } - } + args.Handled(false); + return; } - LOG_IF_FAILED(_OpenNewTab(realArgs.TerminalArgs())); + LOG_IF_FAILED(_OpenNewTab(realArgs.ContentArgs())); args.Handled(true); } } @@ -869,8 +883,23 @@ namespace winrt::TerminalApp::implementation // - // Important: Don't take the param by reference, since we'll be doing work // on another thread. - fire_and_forget TerminalPage::_OpenNewWindow(const NewTerminalArgs newTerminalArgs) + fire_and_forget TerminalPage::_OpenNewWindow(const INewContentArgs newContentArgs) { + auto terminalArgs{ newContentArgs.try_as() }; + + // Do nothing for non-terminal panes. + // + // Theoretically, we could define a `IHasCommandline` interface, and + // stick `ToCommandline` on that interface, for any kind of pane that + // wants to be convertable to a wt commandline. + // + // Another idea we're thinking about is just `wt do {literal json for an + // action}`, which might be less leaky + if (terminalArgs == nullptr) + { + co_return; + } + // Hop to the BG thread co_await winrt::resume_background(); @@ -883,8 +912,7 @@ namespace winrt::TerminalApp::implementation // `-w -1` will ensure a new window is created. winrt::hstring cmdline{ fmt::format(L"-w -1 new-tab {}", - newTerminalArgs ? newTerminalArgs.ToCommandline().c_str() : - L"") + terminalArgs.ToCommandline().c_str()) }; // Build the args to ShellExecuteEx. We need to use ShellExecuteEx so we @@ -909,29 +937,32 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleNewWindow(const IInspectable& /*sender*/, const ActionEventArgs& actionArgs) { - NewTerminalArgs newTerminalArgs{ nullptr }; + INewContentArgs newContentArgs{ nullptr }; // If the caller provided NewTerminalArgs, then try to use those if (actionArgs) { if (const auto& realArgs = actionArgs.ActionArgs().try_as()) { - newTerminalArgs = realArgs.TerminalArgs(); + newContentArgs = realArgs.ContentArgs(); } } // Otherwise, if no NewTerminalArgs were provided, then just use a // default-constructed one. The default-constructed one implies that // nothing about the launch should be modified (just use the default // profile). - if (!newTerminalArgs) + if (!newContentArgs) { - newTerminalArgs = NewTerminalArgs(); + newContentArgs = NewTerminalArgs{}; } - const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; + if (const auto& terminalArgs{ newContentArgs.try_as() }) + { + const auto profile{ _settings.GetProfileForArgs(terminalArgs) }; + terminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid())); + } // Manually fill in the evaluated profile. - newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid())); - _OpenNewWindow(newTerminalArgs); + _OpenNewWindow(newContentArgs); actionArgs.Handled(true); } diff --git a/src/cascadia/TerminalApp/IPaneContent.idl b/src/cascadia/TerminalApp/IPaneContent.idl index fa258fdbd26..f4c6ce13957 100644 --- a/src/cascadia/TerminalApp/IPaneContent.idl +++ b/src/cascadia/TerminalApp/IPaneContent.idl @@ -31,7 +31,7 @@ namespace TerminalApp Windows.Foundation.IReference TabColor { get; }; Windows.UI.Xaml.Media.Brush BackgroundBrush { get; }; - Microsoft.Terminal.Settings.Model.NewTerminalArgs GetNewTerminalArgs(BuildStartupKind kind); + Microsoft.Terminal.Settings.Model.INewContentArgs GetNewTerminalArgs(BuildStartupKind kind); void Focus(Windows.UI.Xaml.FocusState reason); diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 8d159506c15..7d11f4edc63 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -100,7 +100,7 @@ Pane::Pane(std::shared_ptr first, // Extract the terminal settings from the current (leaf) pane's control // to be used to create an equivalent control -NewTerminalArgs Pane::GetTerminalArgsForPane(BuildStartupKind kind) const +INewContentArgs Pane::GetTerminalArgsForPane(BuildStartupKind kind) const { // Leaves are the only things that have controls assert(_IsLeaf()); @@ -1280,21 +1280,11 @@ void Pane::_FocusFirstChild() } } -void Pane::UpdateSettings(const CascadiaSettings& settings, const winrt::TerminalApp::TerminalSettingsCache& cache) +void Pane::UpdateSettings(const CascadiaSettings& settings) { if (_content) { - // We need to do a bit more work here for terminal - // panes. They need to know about the profile that was used for - // them, and about the focused/unfocused settings. - if (const auto& terminalPaneContent{ _content.try_as() }) - { - terminalPaneContent.UpdateTerminalSettings(cache); - } - else - { - _content.UpdateSettings(settings); - } + _content.UpdateSettings(settings); } } diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index f93798b91c2..67daee1694f 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -106,9 +106,9 @@ class Pane : public std::enable_shared_from_this uint32_t panesCreated; }; BuildStartupState BuildStartupActions(uint32_t currentId, uint32_t nextId, winrt::TerminalApp::BuildStartupKind kind); - winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetTerminalArgsForPane(winrt::TerminalApp::BuildStartupKind kind) const; + winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetTerminalArgsForPane(winrt::TerminalApp::BuildStartupKind kind) const; - void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings, const winrt::TerminalApp::TerminalSettingsCache& cache); + void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction); std::shared_ptr NavigateDirection(const std::shared_ptr sourcePane, const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction, diff --git a/src/cascadia/TerminalApp/ScratchpadContent.cpp b/src/cascadia/TerminalApp/ScratchpadContent.cpp index 0f39daa1bee..16346c9b038 100644 --- a/src/cascadia/TerminalApp/ScratchpadContent.cpp +++ b/src/cascadia/TerminalApp/ScratchpadContent.cpp @@ -48,9 +48,9 @@ namespace winrt::TerminalApp::implementation CloseRequested.raise(*this, nullptr); } - NewTerminalArgs ScratchpadContent::GetNewTerminalArgs(const BuildStartupKind /* kind */) const + INewContentArgs ScratchpadContent::GetNewTerminalArgs(const BuildStartupKind /* kind */) const { - return nullptr; + return BaseContentArgs(L"scratchpad"); } winrt::hstring ScratchpadContent::Icon() const diff --git a/src/cascadia/TerminalApp/ScratchpadContent.h b/src/cascadia/TerminalApp/ScratchpadContent.h index cd01414e677..407773fedd1 100644 --- a/src/cascadia/TerminalApp/ScratchpadContent.h +++ b/src/cascadia/TerminalApp/ScratchpadContent.h @@ -19,7 +19,7 @@ namespace winrt::TerminalApp::implementation void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic); void Close(); - winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetNewTerminalArgs(BuildStartupKind kind) const; + winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetNewTerminalArgs(BuildStartupKind kind) const; winrt::hstring Title() { return L"Scratchpad"; } uint64_t TaskbarState() { return 0; } diff --git a/src/cascadia/TerminalApp/SettingsPaneContent.cpp b/src/cascadia/TerminalApp/SettingsPaneContent.cpp index 3e89c38296b..7e48ae9f6be 100644 --- a/src/cascadia/TerminalApp/SettingsPaneContent.cpp +++ b/src/cascadia/TerminalApp/SettingsPaneContent.cpp @@ -50,12 +50,9 @@ namespace winrt::TerminalApp::implementation CloseRequested.raise(*this, nullptr); } - NewTerminalArgs SettingsPaneContent::GetNewTerminalArgs(const BuildStartupKind /*kind*/) const + INewContentArgs SettingsPaneContent::GetNewTerminalArgs(const BuildStartupKind /*kind*/) const { - // For now, we're doing a terrible thing in TerminalTab itself to - // generate an OpenSettings action manually, without asking for the pane - // structure. - return nullptr; + return BaseContentArgs(L"settings"); } winrt::hstring SettingsPaneContent::Icon() const diff --git a/src/cascadia/TerminalApp/SettingsPaneContent.h b/src/cascadia/TerminalApp/SettingsPaneContent.h index ee3322bf06b..6169df84beb 100644 --- a/src/cascadia/TerminalApp/SettingsPaneContent.h +++ b/src/cascadia/TerminalApp/SettingsPaneContent.h @@ -20,7 +20,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::Foundation::Size MinimumSize(); void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic); void Close(); - winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetNewTerminalArgs(const BuildStartupKind kind) const; + winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetNewTerminalArgs(const BuildStartupKind kind) const; winrt::hstring Title() { return RS_(L"SettingsTab"); } uint64_t TaskbarState() { return 0; } diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 3f833bb8ba9..f2039d0043e 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -62,30 +62,33 @@ namespace winrt::TerminalApp::implementation // - existingConnection: An optional connection that is already established to a PTY // for this tab to host instead of creating one. // If not defined, the tab will create the connection. - HRESULT TerminalPage::_OpenNewTab(const NewTerminalArgs& newTerminalArgs) + HRESULT TerminalPage::_OpenNewTab(const INewContentArgs& newContentArgs) try { - const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; - // GH#11114: GetProfileForArgs can return null if the index is higher - // than the number of available profiles. - if (!profile) + if (const auto& newTerminalArgs{ newContentArgs.try_as() }) { - return S_FALSE; - } - const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) }; + const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; + // GH#11114: GetProfileForArgs can return null if the index is higher + // than the number of available profiles. + if (!profile) + { + return S_FALSE; + } + const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) }; - // Try to handle auto-elevation - if (_maybeElevate(newTerminalArgs, settings, profile)) - { - return S_OK; + // Try to handle auto-elevation + if (_maybeElevate(newTerminalArgs, settings, profile)) + { + return S_OK; + } + // We can't go in the other direction (elevated->unelevated) + // unfortunately. This seems to be due to Centennial quirks. It works + // unpackaged, but not packaged. } - // We can't go in the other direction (elevated->unelevated) - // unfortunately. This seems to be due to Centennial quirks. It works - // unpackaged, but not packaged. - // + // This call to _MakePane won't return nullptr, we already checked that // case above with the _maybeElevate call. - _CreateNewTabFromPane(_MakePane(newTerminalArgs, nullptr)); + _CreateNewTabFromPane(_MakePane(newContentArgs, nullptr)); return S_OK; } CATCH_RETURN(); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index e722a744112..2f1b66b561a 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -21,6 +21,7 @@ #include "ColorHelper.h" #include "DebugTapConnection.h" #include "SettingsPaneContent.h" +#include "ScratchpadContent.h" #include "TabRowControl.h" #include "Utils.h" @@ -65,7 +66,6 @@ namespace winrt::TerminalApp::implementation _WindowProperties{ std::move(properties) } { InitializeComponent(); - _WindowProperties.PropertyChanged({ get_weak(), &TerminalPage::_windowPropertyChanged }); } @@ -110,7 +110,11 @@ namespace winrt::TerminalApp::implementation void TerminalPage::SetSettings(CascadiaSettings settings, bool needRefreshUI) { assert(Dispatcher().HasThreadAccess()); - + if (_settings == nullptr) + { + // Create this only on the first time we load the settings. + _terminalSettingsCache = TerminalApp::TerminalSettingsCache{ settings, *_bindings }; + } _settings = settings; // Make sure to call SetCommands before _RefreshUIForSettingsReload. @@ -312,6 +316,7 @@ namespace winrt::TerminalApp::implementation // Check that there's at least one action that's not just an elevated newTab action. for (const auto& action : _startupActions) { + // Only new terminal panes will be requesting elevation. NewTerminalArgs newTerminalArgs{ nullptr }; if (action.Action() == ShortcutAction::NewTab) @@ -319,7 +324,7 @@ namespace winrt::TerminalApp::implementation const auto& args{ action.Args().try_as() }; if (args) { - newTerminalArgs = args.TerminalArgs(); + newTerminalArgs = args.ContentArgs().try_as(); } else { @@ -332,7 +337,7 @@ namespace winrt::TerminalApp::implementation const auto& args{ action.Args().try_as() }; if (args) { - newTerminalArgs = args.TerminalArgs(); + newTerminalArgs = args.ContentArgs().try_as(); } else { @@ -3092,9 +3097,9 @@ namespace winrt::TerminalApp::implementation // - If the newTerminalArgs required us to open the pane as a new elevated // connection, then we'll return nullptr. Otherwise, we'll return a new // Pane for this connection. - std::shared_ptr TerminalPage::_MakePane(const NewTerminalArgs& newTerminalArgs, - const winrt::TerminalApp::TabBase& sourceTab, - TerminalConnection::ITerminalConnection existingConnection) + std::shared_ptr TerminalPage::_MakeTerminalPane(const NewTerminalArgs& newTerminalArgs, + const winrt::TerminalApp::TabBase& sourceTab, + TerminalConnection::ITerminalConnection existingConnection) { // First things first - Check for making a pane from content ID. if (newTerminalArgs && @@ -3104,7 +3109,7 @@ namespace winrt::TerminalApp::implementation // serialize the actual profile's GUID along with the content guid. const auto& profile = _settings.GetProfileForArgs(newTerminalArgs); const auto control = _AttachControlToContent(newTerminalArgs.ContentId()); - auto paneContent{ winrt::make(profile, control) }; + auto paneContent{ winrt::make(profile, _terminalSettingsCache, control) }; return std::make_shared(paneContent); } @@ -3161,14 +3166,15 @@ namespace winrt::TerminalApp::implementation const auto control = _CreateNewControlAndContent(controlSettings, connection); - auto paneContent{ winrt::make(profile, control) }; + auto paneContent{ winrt::make(profile, _terminalSettingsCache, control) }; + auto resultPane = std::make_shared(paneContent); if (debugConnection) // this will only be set if global debugging is on and tap is active { auto newControl = _CreateNewControlAndContent(controlSettings, debugConnection); // Split (auto) with the debug tap. - auto debugContent{ winrt::make(profile, newControl) }; + auto debugContent{ winrt::make(profile, _terminalSettingsCache, newControl) }; auto debugPane = std::make_shared(debugContent); // Since we're doing this split directly on the pane (instead of going through TerminalTab, @@ -3186,6 +3192,41 @@ namespace winrt::TerminalApp::implementation return resultPane; } + std::shared_ptr TerminalPage::_MakePane(const INewContentArgs& contentArgs, + const winrt::TerminalApp::TabBase& sourceTab, + TerminalConnection::ITerminalConnection existingConnection) + + { + if (const auto& newTerminalArgs{ contentArgs.try_as() }) + { + // Terminals are of course special, and have to deal with debug taps, duplicating the tab, etc. + return _MakeTerminalPane(newTerminalArgs, sourceTab, existingConnection); + } + + IPaneContent content{ nullptr }; + + const auto& paneType{ contentArgs.Type() }; + if (paneType == L"scratchpad") + { + const auto& scratchPane{ winrt::make_self() }; + + // This is maybe a little wacky - add our key event handler to the pane + // we made. So that we can get actions for keys that the content didn't + // handle. + scratchPane->GetRoot().KeyDown({ get_weak(), &TerminalPage::_KeyDownHandler }); + + content = *scratchPane; + } + else if (paneType == L"settings") + { + content = _makeSettingsContent(); + } + + assert(content); + + return std::make_shared(content); + } + void TerminalPage::_restartPaneConnection( const TerminalApp::TerminalPaneContent& paneContent, const winrt::Windows::Foundation::IInspectable&) @@ -3281,14 +3322,14 @@ namespace winrt::TerminalApp::implementation // updating terminal panes, so that we don't have to build a _new_ // TerminalSettings for every profile we update - we can just look them // up the previous ones we built. - _terminalSettingsCache = TerminalApp::TerminalSettingsCache{ _settings, *_bindings }; + _terminalSettingsCache.Reset(_settings, *_bindings); for (const auto& tab : _tabs) { if (auto terminalTab{ _GetTerminalTabImpl(tab) }) { // Let the tab know that there are new settings. It's up to each content to decide what to do with them. - terminalTab->UpdateSettings(_settings, _terminalSettingsCache); + terminalTab->UpdateSettings(_settings); // Update the icon of the tab for the currently focused profile in that tab. // Only do this for TerminalTabs. Other types of tabs won't have multiple panes @@ -3838,6 +3879,39 @@ namespace winrt::TerminalApp::implementation CATCH_RETURN() } + TerminalApp::IPaneContent TerminalPage::_makeSettingsContent() + { + if (auto app{ winrt::Windows::UI::Xaml::Application::Current().try_as() }) + { + if (auto appPrivate{ winrt::get_self(app) }) + { + // Lazily load the Settings UI components so that we don't do it on startup. + appPrivate->PrepareForSettingsUI(); + } + } + + // Create the SUI pane content + auto settingsContent{ winrt::make_self(_settings) }; + auto sui = settingsContent->SettingsUI(); + + if (_hostingHwnd) + { + sui.SetHostingWindow(reinterpret_cast(*_hostingHwnd)); + } + + // GH#8767 - let unhandled keys in the SUI try to run commands too. + sui.KeyDown({ get_weak(), &TerminalPage::_KeyDownHandler }); + + sui.OpenJson([weakThis{ get_weak() }](auto&& /*s*/, winrt::Microsoft::Terminal::Settings::Model::SettingsTarget e) { + if (auto page{ weakThis.get() }) + { + page->_LaunchSettings(e); + } + }); + + return *settingsContent; + } + // Method Description: // - Creates a settings UI tab and focuses it. If there's already a settings UI tab open, // just focus the existing one. @@ -3850,36 +3924,8 @@ namespace winrt::TerminalApp::implementation // If we're holding the settings tab's switch command, don't create a new one, switch to the existing one. if (!_settingsTab) { - if (auto app{ winrt::Windows::UI::Xaml::Application::Current().try_as() }) - { - if (auto appPrivate{ winrt::get_self(app) }) - { - // Lazily load the Settings UI components so that we don't do it on startup. - appPrivate->PrepareForSettingsUI(); - } - } - - // Create the SUI pane content - auto settingsContent{ winrt::make_self(_settings) }; - auto sui = settingsContent->SettingsUI(); - - if (_hostingHwnd) - { - sui.SetHostingWindow(reinterpret_cast(*_hostingHwnd)); - } - - // GH#8767 - let unhandled keys in the SUI try to run commands too. - sui.KeyDown({ this, &TerminalPage::_KeyDownHandler }); - - sui.OpenJson([weakThis{ get_weak() }](auto&& /*s*/, winrt::Microsoft::Terminal::Settings::Model::SettingsTarget e) { - if (auto page{ weakThis.get() }) - { - page->_LaunchSettings(e); - } - }); - // Create the tab - auto resultPane = std::make_shared(*settingsContent); + auto resultPane = std::make_shared(_makeSettingsContent()); _settingsTab = _CreateNewTabFromPane(resultPane); } else diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index eece631419b..1fd2acfaee6 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -292,7 +292,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _CreateNewTabFlyoutProfile(const Microsoft::Terminal::Settings::Model::Profile profile, int profileIndex); void _OpenNewTabDropdown(); - HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs); + HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs); TerminalApp::TerminalTab _CreateNewTabFromPane(std::shared_ptr pane, uint32_t insertPosition = -1); std::wstring _evaluatePathForCwd(std::wstring_view path); @@ -301,7 +301,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _duplicateConnectionForRestart(const TerminalApp::TerminalPaneContent& paneContent); void _restartPaneConnection(const TerminalApp::TerminalPaneContent&, const winrt::Windows::Foundation::IInspectable&); - winrt::fire_and_forget _OpenNewWindow(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs); + winrt::fire_and_forget _OpenNewWindow(const Microsoft::Terminal::Settings::Model::INewContentArgs newContentArgs); void _OpenNewTerminalViaDropdown(const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs); @@ -435,7 +435,11 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Control::TermControl _SetupControl(const winrt::Microsoft::Terminal::Control::TermControl& term); winrt::Microsoft::Terminal::Control::TermControl _AttachControlToContent(const uint64_t& contentGuid); - std::shared_ptr _MakePane(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr, + TerminalApp::IPaneContent _makeSettingsContent(); + std::shared_ptr _MakeTerminalPane(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr, + const winrt::TerminalApp::TabBase& sourceTab = nullptr, + winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); + std::shared_ptr _MakePane(const Microsoft::Terminal::Settings::Model::INewContentArgs& newContentArgs = nullptr, const winrt::TerminalApp::TabBase& sourceTab = nullptr, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index 96a84c4560f..652486e441a 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -20,8 +20,10 @@ using namespace winrt::Microsoft::Terminal::TerminalConnection; namespace winrt::TerminalApp::implementation { TerminalPaneContent::TerminalPaneContent(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, + const TerminalApp::TerminalSettingsCache& cache, const winrt::Microsoft::Terminal::Control::TermControl& control) : _control{ control }, + _cache{ cache }, _profile{ profile } { _setupControlEvents(); @@ -90,7 +92,7 @@ namespace winrt::TerminalApp::implementation return _control.TabColor(); } - NewTerminalArgs TerminalPaneContent::GetNewTerminalArgs(const BuildStartupKind kind) const + INewContentArgs TerminalPaneContent::GetNewTerminalArgs(const BuildStartupKind kind) const { NewTerminalArgs args{}; const auto& controlSettings = _control.Settings(); @@ -340,15 +342,7 @@ namespace winrt::TerminalApp::implementation void TerminalPaneContent::UpdateSettings(const CascadiaSettings& /*settings*/) { - // Do nothing. We'll later be updated manually by - // UpdateTerminalSettings, which we need for profile and - // focused/unfocused settings. - assert(false); // If you hit this, you done goofed. - } - - void TerminalPaneContent::UpdateTerminalSettings(const TerminalApp::TerminalSettingsCache& cache) - { - if (const auto& settings{ cache.TryLookup(_profile) }) + if (const auto& settings{ _cache.TryLookup(_profile) }) { _control.UpdateControlSettings(settings.DefaultSettings(), settings.UnfocusedSettings()); } diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.h b/src/cascadia/TerminalApp/TerminalPaneContent.h index 57a9a630c0f..2995e5f1872 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.h +++ b/src/cascadia/TerminalApp/TerminalPaneContent.h @@ -19,6 +19,7 @@ namespace winrt::TerminalApp::implementation struct TerminalPaneContent : TerminalPaneContentT { TerminalPaneContent(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, + const TerminalApp::TerminalSettingsCache& cache, const winrt::Microsoft::Terminal::Control::TermControl& control); winrt::Windows::UI::Xaml::FrameworkElement GetRoot(); @@ -27,10 +28,9 @@ namespace winrt::TerminalApp::implementation void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic); void Close(); - winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetNewTerminalArgs(BuildStartupKind kind) const; + winrt::Microsoft::Terminal::Settings::Model::INewContentArgs GetNewTerminalArgs(BuildStartupKind kind) const; void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); - void UpdateTerminalSettings(const TerminalApp::TerminalSettingsCache& cache); void MarkAsDefterm(); @@ -64,6 +64,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr }; winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected }; winrt::Microsoft::Terminal::Settings::Model::Profile _profile{ nullptr }; + TerminalApp::TerminalSettingsCache _cache{ nullptr }; bool _isDefTermSession{ false }; winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr }; diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.idl b/src/cascadia/TerminalApp/TerminalPaneContent.idl index 60c540273c6..0e4738fff42 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.idl +++ b/src/cascadia/TerminalApp/TerminalPaneContent.idl @@ -10,8 +10,6 @@ namespace TerminalApp { Microsoft.Terminal.Control.TermControl GetTermControl(); - void UpdateTerminalSettings(TerminalSettingsCache cache); - void MarkAsDefterm(); Microsoft.Terminal.Settings.Model.Profile GetProfile(); diff --git a/src/cascadia/TerminalApp/TerminalSettingsCache.cpp b/src/cascadia/TerminalApp/TerminalSettingsCache.cpp index 6662afa9384..c6b66169f39 100644 --- a/src/cascadia/TerminalApp/TerminalSettingsCache.cpp +++ b/src/cascadia/TerminalApp/TerminalSettingsCache.cpp @@ -14,25 +14,9 @@ namespace winrt namespace winrt::TerminalApp::implementation { - TerminalSettingsCache::TerminalSettingsCache(const MTSM::CascadiaSettings& settings, const TerminalApp::AppKeyBindings& bindings) : - _settings{ settings }, - _bindings{ bindings } + TerminalSettingsCache::TerminalSettingsCache(const MTSM::CascadiaSettings& settings, const TerminalApp::AppKeyBindings& bindings) { - // Mapping by GUID isn't _excellent_ because the defaults profile doesn't have a stable GUID; however, - // when we stabilize its guid this will become fully safe. - const auto profileDefaults{ _settings.ProfileDefaults() }; - const auto allProfiles{ _settings.AllProfiles() }; - - profileGuidSettingsMap.reserve(allProfiles.Size() + 1); - - // Include the Defaults profile for consideration - profileGuidSettingsMap.insert_or_assign(profileDefaults.Guid(), std::pair{ profileDefaults, nullptr }); - for (const auto& newProfile : allProfiles) - { - // Avoid creating a TerminalSettings right now. They're not totally cheap, and we suspect that users with many - // panes may not be using all of their profiles at the same time. Lazy evaluation is king! - profileGuidSettingsMap.insert_or_assign(newProfile.Guid(), std::pair{ newProfile, nullptr }); - } + Reset(settings, bindings); } MTSM::TerminalSettingsCreateResult TerminalSettingsCache::TryLookup(const MTSM::Profile& profile) @@ -54,4 +38,26 @@ namespace winrt::TerminalApp::implementation return nullptr; } + + void TerminalSettingsCache::Reset(const MTSM::CascadiaSettings& settings, const TerminalApp::AppKeyBindings& bindings) + { + _settings = settings; + _bindings = bindings; + + // Mapping by GUID isn't _excellent_ because the defaults profile doesn't have a stable GUID; however, + // when we stabilize its guid this will become fully safe. + const auto profileDefaults{ _settings.ProfileDefaults() }; + const auto allProfiles{ _settings.AllProfiles() }; + profileGuidSettingsMap.clear(); + profileGuidSettingsMap.reserve(allProfiles.Size() + 1); + + // Include the Defaults profile for consideration + profileGuidSettingsMap.insert_or_assign(profileDefaults.Guid(), std::pair{ profileDefaults, nullptr }); + for (const auto& newProfile : allProfiles) + { + // Avoid creating a TerminalSettings right now. They're not totally cheap, and we suspect that users with many + // panes may not be using all of their profiles at the same time. Lazy evaluation is king! + profileGuidSettingsMap.insert_or_assign(newProfile.Guid(), std::pair{ newProfile, nullptr }); + } + } } diff --git a/src/cascadia/TerminalApp/TerminalSettingsCache.h b/src/cascadia/TerminalApp/TerminalSettingsCache.h index 054ccb4b51a..ec1cd42c655 100644 --- a/src/cascadia/TerminalApp/TerminalSettingsCache.h +++ b/src/cascadia/TerminalApp/TerminalSettingsCache.h @@ -23,6 +23,7 @@ namespace winrt::TerminalApp::implementation public: TerminalSettingsCache(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings, const TerminalApp::AppKeyBindings& bindings); Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult TryLookup(const Microsoft::Terminal::Settings::Model::Profile& profile); + void Reset(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings, const TerminalApp::AppKeyBindings& bindings); private: Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; diff --git a/src/cascadia/TerminalApp/TerminalSettingsCache.idl b/src/cascadia/TerminalApp/TerminalSettingsCache.idl index 933e035a984..24d766c1872 100644 --- a/src/cascadia/TerminalApp/TerminalSettingsCache.idl +++ b/src/cascadia/TerminalApp/TerminalSettingsCache.idl @@ -8,5 +8,6 @@ namespace TerminalApp { TerminalSettingsCache(Microsoft.Terminal.Settings.Model.CascadiaSettings settings, AppKeyBindings bindings); Microsoft.Terminal.Settings.Model.TerminalSettingsCreateResult TryLookup(Microsoft.Terminal.Settings.Model.Profile profile); + void Reset(Microsoft.Terminal.Settings.Model.CascadiaSettings settings, AppKeyBindings bindings); } } diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 9c99b44afaf..ceb3860f1ff 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -267,7 +267,7 @@ namespace winrt::TerminalApp::implementation // of the settings that apply to all tabs. // Return Value: // - - void TerminalTab::UpdateSettings(const CascadiaSettings& settings, const TerminalApp::TerminalSettingsCache& cache) + void TerminalTab::UpdateSettings(const CascadiaSettings& settings) { ASSERT_UI_THREAD(); @@ -276,7 +276,7 @@ namespace winrt::TerminalApp::implementation // Update the settings on all our panes. _rootPane->WalkTree([&](auto pane) { - pane->UpdateSettings(settings, cache); + pane->UpdateSettings(settings); return false; }); } @@ -390,7 +390,7 @@ namespace winrt::TerminalApp::implementation return RS_(L"MultiplePanes"); } const auto activeContent = GetActiveContent(); - return activeContent ? activeContent.Title() : winrt::hstring{ L"" }; + return activeContent ? activeContent.Title() : winrt::hstring{}; } // Method Description: @@ -447,30 +447,7 @@ namespace winrt::TerminalApp::implementation // 1 for the child after the first split. auto state = _rootPane->BuildStartupActions(0, 1, kind); - // HORRIBLE - // - // Workaround till we know how we actually want to handle state - // restoring other kinda of panes. If this is a settings tab, just - // restore it as a settings tab. Don't bother recreating terminal args - // for every pane. - // - // In the future, we'll want to definitely get rid of - // Pane::GetTerminalArgsForPane, and somehow instead find a better way - // of re-creating the pane state. Probably through a combo of ResizePane - // actions and SetPaneOrientation actions. - if (const auto& settings{ _rootPane->GetContent().try_as() }) { - ActionAndArgs action; - action.Action(ShortcutAction::OpenSettings); - OpenSettingsArgs args{ SettingsTarget::SettingsUI }; - action.Args(args); - - state.args = std::vector{ std::move(action) }; - } - else - { - state = _rootPane->BuildStartupActions(0, 1, kind); - ActionAndArgs newTabAction{}; newTabAction.Action(ShortcutAction::NewTab); NewTabArgs newTabArgs{ state.firstPane->GetTerminalArgsForPane(kind) }; diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index 785d785ff31..16d74567c78 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -58,7 +58,7 @@ namespace winrt::TerminalApp::implementation bool SwapPane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction); bool FocusPane(const uint32_t id); - void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings, const TerminalApp::TerminalSettingsCache& cache); + void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); void UpdateTitle(); void Shutdown() override; diff --git a/src/cascadia/TerminalApp/TerminalWindow.cpp b/src/cascadia/TerminalApp/TerminalWindow.cpp index 4a37b0a733e..bd246bb53de 100644 --- a/src/cascadia/TerminalApp/TerminalWindow.cpp +++ b/src/cascadia/TerminalApp/TerminalWindow.cpp @@ -1238,7 +1238,7 @@ namespace winrt::TerminalApp::implementation // Create the equivalent NewTab action. const auto newAction = Settings::Model::ActionAndArgs{ Settings::Model::ShortcutAction::NewTab, Settings::Model::NewTabArgs(firstAction.Args() ? - firstAction.Args().try_as().TerminalArgs() : + firstAction.Args().try_as().ContentArgs() : nullptr) }; args.SetAt(0, newAction); } diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 4c36320d9b2..fbe2eaafb77 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1928,7 +1928,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto cursorPos{ _terminal->GetCursorPosition() }; // Does the current buffer line have a mark on it? - const auto& marks{ _terminal->GetScrollMarks() }; + const auto& marks{ _terminal->GetMarkExtents() }; if (!marks.empty()) { const auto& last{ marks.back() }; @@ -2144,32 +2144,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto& textBuffer = _terminal->GetTextBuffer(); std::vector commands; - - for (const auto& mark : _terminal->GetScrollMarks()) + const auto bufferCommands{ textBuffer.Commands() }; + for (const auto& commandInBuffer : bufferCommands) { - // The command text is between the `end` (which denotes the end of - // the prompt) and the `commandEnd`. - bool markHasCommand = mark.commandEnd.has_value() && - mark.commandEnd != mark.end; - if (!markHasCommand) - { - continue; - } - - // Get the text of the command - const auto line = mark.end.y; - const auto& row = textBuffer.GetRowByOffset(line); - const auto commandText = row.GetText(mark.end.x, mark.commandEnd->x); - - // Trim off trailing spaces. - const auto strEnd = commandText.find_last_not_of(UNICODE_SPACE); + const auto strEnd = commandInBuffer.find_last_not_of(UNICODE_SPACE); if (strEnd != std::string::npos) { - const auto trimmed = commandText.substr(0, strEnd + 1); + const auto trimmed = commandInBuffer.substr(0, strEnd + 1); commands.push_back(winrt::hstring{ trimmed }); } } + auto context = winrt::make_self(std::move(commands)); context->CurrentCommandline(winrt::hstring{ _terminal->CurrentCommand() }); @@ -2381,20 +2367,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation _owningHwnd = owner; } + // This one is fairly hot! it gets called every time we redraw the scrollbar + // marks, which is frequently. Fortunately, we don't need to bother with + // collecting up the actual extents of the marks in here - we just need the + // rows they start on. Windows::Foundation::Collections::IVector ControlCore::ScrollMarks() const { const auto lock = _terminal->LockForReading(); - const auto& internalMarks = _terminal->GetScrollMarks(); + const auto& markRows = _terminal->GetMarkRows(); std::vector v; - v.reserve(internalMarks.size()); + v.reserve(markRows.size()); - for (const auto& mark : internalMarks) + for (const auto& mark : markRows) { v.emplace_back( - mark.start.to_core_point(), - mark.end.to_core_point(), - OptionalFromColor(_terminal->GetColorForMark(mark))); + mark.row, + OptionalFromColor(_terminal->GetColorForMark(mark.data))); } return winrt::single_threaded_vector(std::move(v)); @@ -2403,26 +2392,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::AddMark(const Control::ScrollMark& mark) { const auto lock = _terminal->LockForReading(); - ::ScrollMark m{}; + ::ScrollbarData m{}; if (mark.Color.HasValue) { m.color = til::color{ mark.Color.Color }; } + const auto row = (_terminal->IsSelectionActive()) ? + _terminal->GetSelectionAnchor().y : + _terminal->GetTextBuffer().GetCursor().GetPosition().y; - if (_terminal->IsSelectionActive()) - { - m.start = til::point{ _terminal->GetSelectionAnchor() }; - m.end = til::point{ _terminal->GetSelectionEnd() }; - } - else - { - m.start = m.end = til::point{ _terminal->GetTextBuffer().GetCursor().GetPosition() }; - } - - // The version of this that only accepts a ScrollMark will automatically - // set the start & end to the cursor position. - _terminal->AddMark(m, m.start, m.end, true); + _terminal->AddMarkFromUI(m, row); } void ControlCore::ClearMark() @@ -2441,9 +2421,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation { const auto lock = _terminal->LockForWriting(); const auto currentOffset = ScrollOffset(); - const auto& marks{ _terminal->GetScrollMarks() }; + const auto& marks{ _terminal->GetMarkExtents() }; - std::optional<::ScrollMark> tgt; + std::optional<::MarkExtents> tgt; switch (direction) { @@ -2557,8 +2537,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation const til::point start = _terminal->IsSelectionActive() ? (goUp ? _terminal->GetSelectionAnchor() : _terminal->GetSelectionEnd()) : _terminal->GetTextBuffer().GetCursor().GetPosition(); - std::optional<::ScrollMark> nearest{ std::nullopt }; - const auto& marks{ _terminal->GetScrollMarks() }; + std::optional<::MarkExtents> nearest{ std::nullopt }; + const auto& marks{ _terminal->GetMarkExtents() }; // Early return so we don't have to check for the validity of `nearest` below after the loop exits. if (marks.empty()) @@ -2599,8 +2579,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation const til::point start = _terminal->IsSelectionActive() ? (goUp ? _terminal->GetSelectionAnchor() : _terminal->GetSelectionEnd()) : _terminal->GetTextBuffer().GetCursor().GetPosition(); - std::optional<::ScrollMark> nearest{ std::nullopt }; - const auto& marks{ _terminal->GetScrollMarks() }; + std::optional<::MarkExtents> nearest{ std::nullopt }; + const auto& marks{ _terminal->GetMarkExtents() }; static constexpr til::point worst{ til::CoordTypeMax, til::CoordTypeMax }; til::point bestDistance{ worst }; @@ -2676,8 +2656,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void ControlCore::_contextMenuSelectMark( const til::point& pos, - bool (*filter)(const ::ScrollMark&), - til::point_span (*getSpan)(const ::ScrollMark&)) + bool (*filter)(const ::MarkExtents&), + til::point_span (*getSpan)(const ::MarkExtents&)) { const auto lock = _terminal->LockForWriting(); @@ -2686,7 +2666,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation { return; } - const auto& marks{ _terminal->GetScrollMarks() }; + const auto& marks{ _terminal->GetMarkExtents() }; + for (auto&& m : marks) { // If the caller gave us a way to filter marks, check that now. @@ -2712,20 +2693,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _contextMenuSelectMark( _contextMenuBufferPosition, - [](const ::ScrollMark& m) -> bool { return !m.HasCommand(); }, - [](const ::ScrollMark& m) { return til::point_span{ m.end, *m.commandEnd }; }); + [](const ::MarkExtents& m) -> bool { return !m.HasCommand(); }, + [](const ::MarkExtents& m) { return til::point_span{ m.end, *m.commandEnd }; }); } void ControlCore::ContextMenuSelectOutput() { _contextMenuSelectMark( _contextMenuBufferPosition, - [](const ::ScrollMark& m) -> bool { return !m.HasOutput(); }, - [](const ::ScrollMark& m) { return til::point_span{ *m.commandEnd, *m.outputEnd }; }); + [](const ::MarkExtents& m) -> bool { return !m.HasOutput(); }, + [](const ::MarkExtents& m) { return til::point_span{ *m.commandEnd, *m.outputEnd }; }); } bool ControlCore::_clickedOnMark( const til::point& pos, - bool (*filter)(const ::ScrollMark&)) + bool (*filter)(const ::MarkExtents&)) { const auto lock = _terminal->LockForWriting(); @@ -2738,13 +2719,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation } // DO show this if the click was on a mark with a command - const auto& marks{ _terminal->GetScrollMarks() }; + const auto& marks{ _terminal->GetMarkExtents() }; for (auto&& m : marks) { if (filter && filter(m)) { continue; } + const auto [start, end] = m.GetExtent(); if (start <= pos && end >= pos) @@ -2765,7 +2747,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { // Relies on the anchor set in AnchorContextMenu return _clickedOnMark(_contextMenuBufferPosition, - [](const ::ScrollMark& m) -> bool { return !m.HasCommand(); }); + [](const ::MarkExtents& m) -> bool { return !m.HasCommand(); }); } // Method Description: @@ -2774,6 +2756,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation { // Relies on the anchor set in AnchorContextMenu return _clickedOnMark(_contextMenuBufferPosition, - [](const ::ScrollMark& m) -> bool { return !m.HasOutput(); }); + [](const ::MarkExtents& m) -> bool { return !m.HasOutput(); }); } } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index ee0d68f6a8a..9b6acfcc8e1 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -411,10 +411,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _contextMenuSelectMark( const til::point& pos, - bool (*filter)(const ::ScrollMark&), - til::point_span (*getSpan)(const ::ScrollMark&)); + bool (*filter)(const ::MarkExtents&), + til::point_span (*getSpan)(const ::MarkExtents&)); - bool _clickedOnMark(const til::point& pos, bool (*filter)(const ::ScrollMark&)); + bool _clickedOnMark(const til::point& pos, bool (*filter)(const ::MarkExtents&)); inline bool _IsClosing() const noexcept { diff --git a/src/cascadia/TerminalControl/ICoreState.idl b/src/cascadia/TerminalControl/ICoreState.idl index 0073b70788a..a3333473c28 100644 --- a/src/cascadia/TerminalControl/ICoreState.idl +++ b/src/cascadia/TerminalControl/ICoreState.idl @@ -13,11 +13,8 @@ namespace Microsoft.Terminal.Control struct ScrollMark { - // There are other members of DispatchTypes::ScrollMark, but these are - // all we need to expose up and set downwards currently. Additional - // members can be bubbled as necessary. - Microsoft.Terminal.Core.Point Start; - Microsoft.Terminal.Core.Point End; // exclusive + // Additional members can be bubbled as necessary. + Int32 Row; Microsoft.Terminal.Core.OptionalColor Color; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index ec9807dcd08..06f2ef89709 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -369,7 +369,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { for (const auto& m : marks) { - const auto row = m.Start.Y; + const auto row = m.Row; const til::color color{ m.Color.Color }; const auto base = dataAt(row); drawPip(base, color); diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 4a6c1187da5..9d3dd1d1097 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -397,6 +397,9 @@ try proposedTop = ::base::ClampSub(proposedTop, ::base::ClampSub(proposedBottom, bufferSize.height)); } + // Keep the cursor in the mutable viewport + proposedTop = std::min(proposedTop, newCursorPos.y); + _mutableViewport = Viewport::FromDimensions({ 0, proposedTop }, viewportSize); _mainBuffer.swap(newTextBuffer); @@ -720,20 +723,26 @@ TerminalInput::OutputType Terminal::SendCharEvent(const wchar_t ch, const WORD s // Then treat this line like it's a prompt mark. if (_autoMarkPrompts && vkey == VK_RETURN && !_inAltBuffer()) { - // * If we have a current prompt: - // - Then we did know that the prompt started, (we may have also - // already gotten a MarkCommandStart sequence). The user has pressed - // enter, and we're treating that like the prompt has now ended. - // - Perform a FTCS_COMMAND_EXECUTED, so that we start marking this - // as output. - // - This enables CMD to have full FTCS support, even though there's - // no point in CMD to insert a "pre exec" hook - // * Else: We don't have a prompt. We don't know anything else, but we - // can set the whole line as the prompt, no command, and start the - // command_executed now. + // We need to be a little tricky here, to try and support folks that are + // auto-marking prompts, but don't necessarily have the rest of shell + // integration enabled. + // + // We'll set the current attributes to Output, so that the output after + // here is marked as the command output. But we also need to make sure + // that a mark was started. + // We can't just check if the current row has a mark - there could be a + // multiline prompt. // - // Fortunately, MarkOutputStart will do all this logic for us! - MarkOutputStart(); + // (TextBuffer::_createPromptMarkIfNeeded does that work for us) + + const bool createdMark = _activeBuffer().StartOutput(); + if (createdMark) + { + _activeBuffer().ManuallyMarkRowAsPrompt(_activeBuffer().GetCursor().GetPosition().y); + + // This changed the scrollbar marks - raise a notification to update them + _NotifyScrollEvent(); + } } const auto keyDown = SynthesizeKeyEvent(true, 1, vkey, scanCode, ch, states.Value()); @@ -1435,36 +1444,19 @@ PointTree Terminal::_getPatterns(til::CoordType beg, til::CoordType end) const } // NOTE: This is the version of AddMark that comes from the UI. The VT api call into this too. -void Terminal::AddMark(const ScrollMark& mark, - const til::point& start, - const til::point& end, - const bool fromUi) +void Terminal::AddMarkFromUI(ScrollbarData mark, + til::CoordType y) { if (_inAltBuffer()) { return; } - ScrollMark m = mark; - m.start = start; - m.end = end; - - // If the mark came from the user adding a mark via the UI, don't make it the active prompt mark. - if (fromUi) - { - _activeBuffer().AddMark(m); - } - else - { - _activeBuffer().StartPromptMark(m); - } + _activeBuffer().SetScrollbarData(mark, y); // Tell the control that the scrollbar has somehow changed. Used as a // workaround to force the control to redraw any scrollbar marks _NotifyScrollEvent(); - - // DON'T set _currentPrompt. The VT impl will do that for you. We don't want - // UI-driven marks to set that. } void Terminal::ClearMark() @@ -1495,30 +1487,30 @@ void Terminal::ClearAllMarks() _NotifyScrollEvent(); } -const std::vector& Terminal::GetScrollMarks() const noexcept +std::vector Terminal::GetMarkRows() const +{ + // We want to return _no_ marks when we're in the alt buffer, to effectively + // hide them. + return _inAltBuffer() ? std::vector{} : _activeBuffer().GetMarkRows(); +} +std::vector Terminal::GetMarkExtents() const { - // TODO: GH#11000 - when the marks are stored per-buffer, get rid of this. // We want to return _no_ marks when we're in the alt buffer, to effectively - // hide them. We need to return a reference, so we can't just ctor an empty - // list here just for when we're in the alt buffer. - return _activeBuffer().GetMarks(); + // hide them. + return _inAltBuffer() ? std::vector{} : _activeBuffer().GetMarkExtents(); } -til::color Terminal::GetColorForMark(const ScrollMark& mark) const +til::color Terminal::GetColorForMark(const ScrollbarData& markData) const { - if (mark.color.has_value()) + if (markData.color.has_value()) { - return *mark.color; + return *markData.color; } const auto& renderSettings = GetRenderSettings(); - switch (mark.category) - { - case MarkCategory::Prompt: + switch (markData.category) { - return renderSettings.GetColorAlias(ColorAlias::DefaultForeground); - } case MarkCategory::Error: { return renderSettings.GetColorTableEntry(TextColor::BRIGHT_RED); @@ -1531,21 +1523,17 @@ til::color Terminal::GetColorForMark(const ScrollMark& mark) const { return renderSettings.GetColorTableEntry(TextColor::BRIGHT_GREEN); } + case MarkCategory::Prompt: + case MarkCategory::Default: default: - case MarkCategory::Info: { return renderSettings.GetColorAlias(ColorAlias::DefaultForeground); } } } -std::wstring_view Terminal::CurrentCommand() const +std::wstring Terminal::CurrentCommand() const { - if (_currentPromptState != PromptState::Command) - { - return L""; - } - return _activeBuffer().CurrentCommand(); } diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index b10c0d893ac..e792ee8d21c 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -117,15 +117,14 @@ class Microsoft::Terminal::Core::Terminal final : RenderSettings& GetRenderSettings() noexcept; const RenderSettings& GetRenderSettings() const noexcept; - const std::vector& GetScrollMarks() const noexcept; - void AddMark(const ScrollMark& mark, - const til::point& start, - const til::point& end, - const bool fromUi); + std::vector GetMarkRows() const; + std::vector GetMarkExtents() const; + void AddMarkFromUI(ScrollbarData mark, til::CoordType y); til::property AlwaysNotifyOnBufferRotation; - std::wstring_view CurrentCommand() const; + std::wstring CurrentCommand() const; + void SerializeMainBuffer(const wchar_t* destination) const; #pragma region ITerminalApi @@ -152,11 +151,6 @@ class Microsoft::Terminal::Core::Terminal final : void UseAlternateScreenBuffer(const TextAttribute& attrs) override; void UseMainScreenBuffer() override; - void MarkPrompt(const ScrollMark& mark) override; - void MarkCommandStart() override; - void MarkOutputStart() override; - void MarkCommandFinish(std::optional error) override; - bool IsConsolePty() const noexcept override; bool IsVtInputEnabled() const noexcept override; void NotifyAccessibilityChange(const til::rect& changedRect) noexcept override; @@ -168,7 +162,7 @@ class Microsoft::Terminal::Core::Terminal final : void ClearMark(); void ClearAllMarks(); - til::color GetColorForMark(const ScrollMark& mark) const; + til::color GetColorForMark(const ScrollbarData& markData) const; #pragma region ITerminalInput // These methods are defined in Terminal.cpp @@ -435,15 +429,6 @@ class Microsoft::Terminal::Core::Terminal final : }; std::optional _lastKeyEventCodes; - enum class PromptState : uint32_t - { - None = 0, - Prompt, - Command, - Output, - }; - PromptState _currentPromptState{ PromptState::None }; - static WORD _ScanCodeFromVirtualKey(const WORD vkey) noexcept; static WORD _VirtualKeyFromScanCode(const WORD scanCode) noexcept; static WORD _VirtualKeyFromCharacter(const wchar_t ch) noexcept; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 9a5ea2221ac..121910cd697 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -300,140 +300,6 @@ void Terminal::UseMainScreenBuffer() _activeBuffer().TriggerRedrawAll(); } -// NOTE: This is the version of AddMark that comes from VT -void Terminal::MarkPrompt(const ScrollMark& mark) -{ - _assertLocked(); - - static bool logged = false; - if (!logged) - { - TraceLoggingWrite( - g_hCTerminalCoreProvider, - "ShellIntegrationMarkAdded", - TraceLoggingDescription("A mark was added via VT at least once"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - - logged = true; - } - - const til::point cursorPos{ _activeBuffer().GetCursor().GetPosition() }; - AddMark(mark, cursorPos, cursorPos, false); - - if (mark.category == MarkCategory::Prompt) - { - _currentPromptState = PromptState::Prompt; - } -} - -void Terminal::MarkCommandStart() -{ - _assertLocked(); - - const til::point cursorPos{ _activeBuffer().GetCursor().GetPosition() }; - - if ((_currentPromptState == PromptState::Prompt) && - (_activeBuffer().GetMarks().size() > 0)) - { - // We were in the right state, and there's a previous mark to work - // with. - // - //We can just do the work below safely. - } - else if (_currentPromptState == PromptState::Command) - { - // We're already in the command state. We don't want to end the current - // mark. We don't want to make a new one. We want to just leave the - // current command going. - return; - } - else - { - // If there was no last mark, or we're in a weird state, - // then abandon the current state, and just insert a new Prompt mark that - // start's & ends here, and got to State::Command. - - ScrollMark mark; - mark.category = MarkCategory::Prompt; - AddMark(mark, cursorPos, cursorPos, false); - } - _activeBuffer().SetCurrentPromptEnd(cursorPos); - _currentPromptState = PromptState::Command; -} - -void Terminal::MarkOutputStart() -{ - _assertLocked(); - - const til::point cursorPos{ _activeBuffer().GetCursor().GetPosition() }; - - if ((_currentPromptState == PromptState::Command) && - (_activeBuffer().GetMarks().size() > 0)) - { - // We were in the right state, and there's a previous mark to work - // with. - // - //We can just do the work below safely. - } - else if (_currentPromptState == PromptState::Output) - { - // We're already in the output state. We don't want to end the current - // mark. We don't want to make a new one. We want to just leave the - // current output going. - return; - } - else - { - // If there was no last mark, or we're in a weird state, - // then abandon the current state, and just insert a new Prompt mark that - // start's & ends here, and the command ends here, and go to State::Output. - - ScrollMark mark; - mark.category = MarkCategory::Prompt; - AddMark(mark, cursorPos, cursorPos, false); - } - _activeBuffer().SetCurrentCommandEnd(cursorPos); - _currentPromptState = PromptState::Output; -} - -void Terminal::MarkCommandFinish(std::optional error) -{ - _assertLocked(); - - const til::point cursorPos{ _activeBuffer().GetCursor().GetPosition() }; - auto category = MarkCategory::Prompt; - if (error.has_value()) - { - category = *error == 0u ? - MarkCategory::Success : - MarkCategory::Error; - } - - if ((_currentPromptState == PromptState::Output) && - (_activeBuffer().GetMarks().size() > 0)) - { - // We were in the right state, and there's a previous mark to work - // with. - // - //We can just do the work below safely. - } - else - { - // If there was no last mark, or we're in a weird state, then abandon - // the current state, and just insert a new Prompt mark that start's & - // ends here, and the command ends here, AND the output ends here. and - // go to State::Output. - - ScrollMark mark; - mark.category = MarkCategory::Prompt; - AddMark(mark, cursorPos, cursorPos, false); - _activeBuffer().SetCurrentCommandEnd(cursorPos); - } - _activeBuffer().SetCurrentOutputEnd(cursorPos, category); - _currentPromptState = PromptState::None; -} - // Method Description: // - Reacts to a client asking us to show or hide the window. // Arguments: @@ -497,16 +363,9 @@ void Terminal::NotifyBufferRotation(const int delta) // manually erase our pattern intervals since the locations have changed now _patternIntervalTree = {}; - auto& marks{ _activeBuffer().GetMarks() }; - const auto hasScrollMarks = marks.size() > 0; - if (hasScrollMarks) - { - _activeBuffer().ScrollMarks(-delta); - } - const auto oldScrollOffset = _scrollOffset; _PreserveUserScrollOffset(delta); - if (_scrollOffset != oldScrollOffset || hasScrollMarks || AlwaysNotifyOnBufferRotation()) + if (_scrollOffset != oldScrollOffset || AlwaysNotifyOnBufferRotation()) { _NotifyScrollEvent(); } diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp index 598b77a4051..24f4e445953 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp @@ -6,6 +6,7 @@ #include "ActionArgs.h" #include "ActionEventArgs.g.cpp" +#include "BaseContentArgs.g.cpp" #include "NewTerminalArgs.g.cpp" #include "CopyTextArgs.g.cpp" #include "NewTabArgs.g.cpp" @@ -245,9 +246,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::hstring NewTabArgs::GenerateName() const { winrt::hstring newTerminalArgsStr; - if (TerminalArgs()) + if (ContentArgs()) { - newTerminalArgsStr = TerminalArgs().GenerateName(); + newTerminalArgsStr = ContentArgs().GenerateName(); } if (newTerminalArgsStr.empty()) @@ -465,9 +466,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } winrt::hstring newTerminalArgsStr; - if (TerminalArgs()) + if (ContentArgs()) { - newTerminalArgsStr = TerminalArgs().GenerateName(); + newTerminalArgsStr = ContentArgs().GenerateName(); } if (SplitMode() != SplitType::Duplicate && !newTerminalArgsStr.empty()) @@ -769,9 +770,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::hstring NewWindowArgs::GenerateName() const { winrt::hstring newTerminalArgsStr; - if (TerminalArgs()) + if (ContentArgs()) { - newTerminalArgsStr = TerminalArgs().GenerateName(); + newTerminalArgsStr = ContentArgs().GenerateName(); } if (newTerminalArgsStr.empty()) diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 4062cee426c..bf34a6af140 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -6,6 +6,7 @@ // HEY YOU: When adding ActionArgs types, make sure to add the corresponding // *.g.cpp to ActionArgs.cpp! #include "ActionEventArgs.g.h" +#include "BaseContentArgs.g.h" #include "NewTerminalArgs.g.h" #include "CopyTextArgs.g.h" #include "NewTabArgs.g.h" @@ -71,7 +72,7 @@ public: \ _##name = value; \ } \ \ -private: \ +protected: \ std::optional _##name{ std::nullopt }; // Notes on defining ActionArgs and ActionEventArgs: @@ -295,6 +296,56 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation WINRT_PROPERTY(bool, Handled, false); }; + struct BaseContentArgs : public BaseContentArgsT + { + BaseContentArgs(winrt::hstring type) : + _Type{ type } {} + + BaseContentArgs() : + BaseContentArgs(L"") {} + + ACTION_ARG(winrt::hstring, Type, L""); + + static constexpr std::string_view TypeKey{ "type" }; + + public: + bool Equals(INewContentArgs other) const + { + return other.Type() == _Type; + } + size_t Hash() const + { + til::hasher h; + Hash(h); + return h.finalize(); + } + void Hash(til::hasher& h) const + { + h.write(Type()); + } + INewContentArgs Copy() const + { + auto copy{ winrt::make_self() }; + copy->_Type = _Type; + return *copy; + } + winrt::hstring GenerateName() const + { + return winrt::hstring{ L"type: " } + Type(); + } + static Json::Value ToJson(const Model::BaseContentArgs& val) + { + if (!val) + { + return {}; + } + auto args{ get_self(val) }; + Json::Value json{ Json::ValueType::objectValue }; + JsonUtils::SetValueForKey(json, TypeKey, args->_Type); + return json; + } + }; + // Although it may _seem_ like NewTerminalArgs can use ACTION_ARG_BODY, it // actually can't, because it isn't an `IActionArgs`, which breaks some // assumptions made in the macro. @@ -303,6 +354,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation NewTerminalArgs() = default; NewTerminalArgs(int32_t& profileIndex) : _ProfileIndex{ profileIndex } {}; + + ACTION_ARG(winrt::hstring, Type, L""); + ACTION_ARG(winrt::hstring, Commandline, L""); ACTION_ARG(winrt::hstring, StartingDirectory, L""); ACTION_ARG(winrt::hstring, TabTitle, L""); @@ -335,7 +389,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation hstring GenerateName() const; hstring ToCommandline() const; - bool Equals(const Model::NewTerminalArgs& other) + bool Equals(const Model::INewContentArgs& other) { auto otherAsUs = other.try_as(); if (otherAsUs) @@ -433,6 +487,45 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation h.write(ContentId()); } }; + + static std::tuple> ContentArgsFromJson(const Json::Value& json) + { + winrt::hstring type; + JsonUtils::GetValueForKey(json, "type", type); + if (type.empty()) + { + auto terminalArgs = winrt::Microsoft::Terminal::Settings::Model::implementation::NewTerminalArgs::FromJson(json); + // Don't let the user specify the __content property in their + // settings. That's an internal-use-only property. + if (terminalArgs.ContentId()) + { + return { terminalArgs, { SettingsLoadWarnings::InvalidUseOfContent } }; + } + return { terminalArgs, {} }; + } + + // For now, we don't support any other concrete types of content + // with args. Just return a placeholder type that only includes the type + return { *winrt::make_self(type), {} }; + } + static Json::Value ContentArgsToJson(const Model::INewContentArgs& contentArgs) + { + if (contentArgs == nullptr) + { + return {}; + } + // TerminalArgs don't have a type. + if (contentArgs.Type().empty()) + { + return winrt::Microsoft::Terminal::Settings::Model::implementation::NewTerminalArgs::ToJson(contentArgs.try_as()); + } + + // For now, we don't support any other concrete types of content + // with args. Just return a placeholder. + auto base{ winrt::make_self(contentArgs.Type()) }; + return BaseContentArgs::ToJson(*base); + } + } template<> @@ -463,6 +556,20 @@ struct til::hash_trait } } }; +template<> +struct til::hash_trait +{ + using M = winrt::Microsoft::Terminal::Settings::Model::INewContentArgs; + + void operator()(hasher& h, const M& value) const noexcept + { + if (value) + { + h.write(value.Type()); + h.write(value.Hash()); + } + } +}; namespace winrt::Microsoft::Terminal::Settings::Model::implementation { @@ -473,9 +580,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct NewTabArgs : public NewTabArgsT { NewTabArgs() = default; - NewTabArgs(const Model::NewTerminalArgs& terminalArgs) : - _TerminalArgs{ terminalArgs } {}; - WINRT_PROPERTY(Model::NewTerminalArgs, TerminalArgs, nullptr); + NewTabArgs(const Model::INewContentArgs& terminalArgs) : + _ContentArgs{ terminalArgs } {}; + WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, nullptr); public: hstring GenerateName() const; @@ -485,7 +592,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation auto otherAsUs = other.try_as(); if (otherAsUs) { - return otherAsUs->_TerminalArgs.Equals(_TerminalArgs); + return otherAsUs->_ContentArgs.Equals(_ContentArgs); } return false; }; @@ -493,15 +600,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // LOAD BEARING: Not using make_self here _will_ break you in the future! auto args = winrt::make_self(); - args->_TerminalArgs = NewTerminalArgs::FromJson(json); - - // Don't let the user specify the __content property in their - // settings. That's an internal-use-only property. - if (args->_TerminalArgs.ContentId()) - { - return { *args, { SettingsLoadWarnings::InvalidUseOfContent } }; - } - return { *args, {} }; + auto [content, warnings] = ContentArgsFromJson(json); + args->_ContentArgs = content; + return { *args, warnings }; } static Json::Value ToJson(const IActionArgs& val) { @@ -510,18 +611,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return {}; } const auto args{ get_self(val) }; - return NewTerminalArgs::ToJson(args->_TerminalArgs); + return ContentArgsToJson(args->_ContentArgs); } IActionArgs Copy() const { auto copy{ winrt::make_self() }; - copy->_TerminalArgs = _TerminalArgs.Copy(); + copy->_ContentArgs = _ContentArgs.Copy(); return *copy; } size_t Hash() const { til::hasher h; - h.write(TerminalArgs()); + h.write(ContentArgs()); return h.finalize(); } }; @@ -529,22 +630,23 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct SplitPaneArgs : public SplitPaneArgsT { SplitPaneArgs() = default; - SplitPaneArgs(SplitType splitMode, SplitDirection direction, double size, const Model::NewTerminalArgs& terminalArgs) : + SplitPaneArgs(SplitType splitMode, SplitDirection direction, double size, const Model::INewContentArgs& terminalArgs) : _SplitMode{ splitMode }, _SplitDirection{ direction }, _SplitSize{ size }, - _TerminalArgs{ terminalArgs } {}; - SplitPaneArgs(SplitDirection direction, double size, const Model::NewTerminalArgs& terminalArgs) : + _ContentArgs{ terminalArgs } {}; + SplitPaneArgs(SplitDirection direction, double size, const Model::INewContentArgs& terminalArgs) : _SplitDirection{ direction }, _SplitSize{ size }, - _TerminalArgs{ terminalArgs } {}; - SplitPaneArgs(SplitDirection direction, const Model::NewTerminalArgs& terminalArgs) : + _ContentArgs{ terminalArgs } {}; + SplitPaneArgs(SplitDirection direction, const Model::INewContentArgs& terminalArgs) : _SplitDirection{ direction }, - _TerminalArgs{ terminalArgs } {}; + _ContentArgs{ terminalArgs } {}; SplitPaneArgs(SplitType splitMode) : _SplitMode{ splitMode } {}; + ACTION_ARG(Model::SplitDirection, SplitDirection, SplitDirection::Automatic); - WINRT_PROPERTY(Model::NewTerminalArgs, TerminalArgs, nullptr); + WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, nullptr); ACTION_ARG(SplitType, SplitMode, SplitType::Manual); ACTION_ARG(double, SplitSize, .5); @@ -561,8 +663,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (otherAsUs) { return otherAsUs->_SplitDirection == _SplitDirection && - (otherAsUs->_TerminalArgs ? otherAsUs->_TerminalArgs.Equals(_TerminalArgs) : - otherAsUs->_TerminalArgs == _TerminalArgs) && + (otherAsUs->_ContentArgs ? otherAsUs->_ContentArgs.Equals(_ContentArgs) : + otherAsUs->_ContentArgs == _ContentArgs) && otherAsUs->_SplitSize == _SplitSize && otherAsUs->_SplitMode == _SplitMode; } @@ -572,7 +674,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // LOAD BEARING: Not using make_self here _will_ break you in the future! auto args = winrt::make_self(); - args->_TerminalArgs = NewTerminalArgs::FromJson(json); JsonUtils::GetValueForKey(json, SplitKey, args->_SplitDirection); JsonUtils::GetValueForKey(json, SplitModeKey, args->_SplitMode); JsonUtils::GetValueForKey(json, SplitSizeKey, args->_SplitSize); @@ -581,14 +682,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return { nullptr, { SettingsLoadWarnings::InvalidSplitSize } }; } - // Don't let the user specify the __content property in their - // settings. That's an internal-use-only property. - if (args->_TerminalArgs.ContentId()) - { - return { *args, { SettingsLoadWarnings::InvalidUseOfContent } }; - } - - return { *args, {} }; + auto [content, warnings] = ContentArgsFromJson(json); + args->_ContentArgs = content; + return { *args, warnings }; } static Json::Value ToJson(const IActionArgs& val) { @@ -597,7 +693,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return {}; } const auto args{ get_self(val) }; - auto json{ NewTerminalArgs::ToJson(args->_TerminalArgs) }; + auto json{ ContentArgsToJson(args->_ContentArgs) }; JsonUtils::SetValueForKey(json, SplitKey, args->_SplitDirection); JsonUtils::SetValueForKey(json, SplitModeKey, args->_SplitMode); JsonUtils::SetValueForKey(json, SplitSizeKey, args->_SplitSize); @@ -607,7 +703,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { auto copy{ winrt::make_self() }; copy->_SplitDirection = _SplitDirection; - copy->_TerminalArgs = _TerminalArgs.Copy(); + copy->_ContentArgs = _ContentArgs.Copy(); copy->_SplitMode = _SplitMode; copy->_SplitSize = _SplitSize; return *copy; @@ -616,7 +712,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { til::hasher h; h.write(SplitDirection()); - h.write(TerminalArgs()); + h.write(ContentArgs()); h.write(SplitMode()); h.write(SplitSize()); return h.finalize(); @@ -626,9 +722,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct NewWindowArgs : public NewWindowArgsT { NewWindowArgs() = default; - NewWindowArgs(const Model::NewTerminalArgs& terminalArgs) : - _TerminalArgs{ terminalArgs } {}; - WINRT_PROPERTY(Model::NewTerminalArgs, TerminalArgs, nullptr); + NewWindowArgs(const Model::INewContentArgs& terminalArgs) : + _ContentArgs{ terminalArgs } {}; + WINRT_PROPERTY(Model::INewContentArgs, ContentArgs, nullptr); public: hstring GenerateName() const; @@ -638,7 +734,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation auto otherAsUs = other.try_as(); if (otherAsUs) { - return otherAsUs->_TerminalArgs.Equals(_TerminalArgs); + return otherAsUs->_ContentArgs.Equals(_ContentArgs); } return false; }; @@ -646,16 +742,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // LOAD BEARING: Not using make_self here _will_ break you in the future! auto args = winrt::make_self(); - args->_TerminalArgs = NewTerminalArgs::FromJson(json); - - // Don't let the user specify the __content property in their - // settings. That's an internal-use-only property. - if (args->_TerminalArgs.ContentId()) - { - return { *args, { SettingsLoadWarnings::InvalidUseOfContent } }; - } - - return { *args, {} }; + auto [content, warnings] = ContentArgsFromJson(json); + args->_ContentArgs = content; + return { *args, warnings }; } static Json::Value ToJson(const IActionArgs& val) { @@ -664,18 +753,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return {}; } const auto args{ get_self(val) }; - return NewTerminalArgs::ToJson(args->_TerminalArgs); + return ContentArgsToJson(args->_ContentArgs); } IActionArgs Copy() const { auto copy{ winrt::make_self() }; - copy->_TerminalArgs = _TerminalArgs.Copy(); + copy->_ContentArgs = _ContentArgs.Copy(); return *copy; } size_t Hash() const { til::hasher h; - h.write(TerminalArgs()); + h.write(ContentArgs()); return h.finalize(); } }; @@ -829,6 +918,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation { BASIC_FACTORY(ActionEventArgs); + BASIC_FACTORY(BaseContentArgs); BASIC_FACTORY(CopyTextArgs); BASIC_FACTORY(SwitchToTabArgs); BASIC_FACTORY(NewTerminalArgs); diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index f8e6f4eb9cd..05ed70069a9 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -122,10 +122,22 @@ namespace Microsoft.Terminal.Settings.Model All = 0xffffffff, }; - [default_interface] runtimeclass NewTerminalArgs { + interface INewContentArgs { + String Type { get; }; + Boolean Equals(INewContentArgs other); + UInt64 Hash(); + INewContentArgs Copy(); + String GenerateName(); + }; + + runtimeclass BaseContentArgs : [default] INewContentArgs { + BaseContentArgs(); + BaseContentArgs(String type); + }; + + runtimeclass NewTerminalArgs : INewContentArgs { NewTerminalArgs(); NewTerminalArgs(Int32 profileIndex); - NewTerminalArgs Copy(); String Commandline; String StartingDirectory; @@ -153,10 +165,7 @@ namespace Microsoft.Terminal.Settings.Model UInt64 ContentId{ get; set; }; - Boolean Equals(NewTerminalArgs other); - String GenerateName(); String ToCommandline(); - UInt64 Hash(); }; [default_interface] runtimeclass ActionEventArgs : IActionEventArgs @@ -175,8 +184,8 @@ namespace Microsoft.Terminal.Settings.Model [default_interface] runtimeclass NewTabArgs : IActionArgs { - NewTabArgs(NewTerminalArgs terminalArgs); - NewTerminalArgs TerminalArgs { get; }; + NewTabArgs(INewContentArgs contentArgs); + INewContentArgs ContentArgs { get; }; }; [default_interface] runtimeclass MovePaneArgs : IActionArgs @@ -221,13 +230,13 @@ namespace Microsoft.Terminal.Settings.Model [default_interface] runtimeclass SplitPaneArgs : IActionArgs { - SplitPaneArgs(SplitType splitMode, SplitDirection split, Double size, NewTerminalArgs terminalArgs); - SplitPaneArgs(SplitDirection split, Double size, NewTerminalArgs terminalArgs); - SplitPaneArgs(SplitDirection split, NewTerminalArgs terminalArgs); + SplitPaneArgs(SplitType splitMode, SplitDirection split, Double size, INewContentArgs contentArgs); + SplitPaneArgs(SplitDirection split, Double size, INewContentArgs contentArgs); + SplitPaneArgs(SplitDirection split, INewContentArgs contentArgs); SplitPaneArgs(SplitType splitMode); SplitDirection SplitDirection { get; }; - NewTerminalArgs TerminalArgs { get; }; + INewContentArgs ContentArgs { get; }; SplitType SplitMode { get; }; Double SplitSize { get; }; }; @@ -347,8 +356,8 @@ namespace Microsoft.Terminal.Settings.Model [default_interface] runtimeclass NewWindowArgs : IActionArgs { - NewWindowArgs(NewTerminalArgs terminalArgs); - NewTerminalArgs TerminalArgs { get; }; + NewWindowArgs(INewContentArgs contentArgs); + INewContentArgs ContentArgs { get; }; }; [default_interface] runtimeclass PrevTabArgs : IActionArgs diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index cde90f910b1..0375d2802ff 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -93,8 +93,8 @@ Author(s): X(bool, RightClickContextMenu, "experimental.rightClickContextMenu", false) \ X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ X(bool, Elevate, "elevate", false) \ - X(bool, AutoMarkPrompts, "experimental.autoMarkPrompts", false) \ - X(bool, ShowMarks, "experimental.showMarksOnScrollbar", false) \ + X(bool, AutoMarkPrompts, "autoMarkPrompts", false) \ + X(bool, ShowMarks, "showMarksOnScrollbar", false) \ X(bool, RepositionCursorWithMouse, "experimental.repositionCursorWithMouse", false) \ X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) diff --git a/src/cascadia/TerminalSettingsModel/Profile.cpp b/src/cascadia/TerminalSettingsModel/Profile.cpp index 0db012e7bc7..0f568b37e53 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.cpp +++ b/src/cascadia/TerminalSettingsModel/Profile.cpp @@ -34,6 +34,9 @@ static constexpr std::string_view PaddingKey{ "padding" }; static constexpr std::string_view TabColorKey{ "tabColor" }; static constexpr std::string_view UnfocusedAppearanceKey{ "unfocusedAppearance" }; +static constexpr std::string_view LegacyAutoMarkPromptsKey{ "experimental.autoMarkPrompts" }; +static constexpr std::string_view LegacyShowMarksKey{ "experimental.showMarksOnScrollbar" }; + Profile::Profile(guid guid) noexcept : _Guid(guid) { @@ -182,6 +185,11 @@ void Profile::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, TabColorKey, _TabColor); + // Try to load some legacy keys, to migrate them. + // Done _before_ the MTSM_PROFILE_SETTINGS, which have the updated keys. + JsonUtils::GetValueForKey(json, LegacyShowMarksKey, _ShowMarks); + JsonUtils::GetValueForKey(json, LegacyAutoMarkPromptsKey, _AutoMarkPrompts); + #define PROFILE_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \ JsonUtils::GetValueForKey(json, jsonKey, _##name); diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index ae8ed369ff6..7c33e169994 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -484,9 +484,11 @@ { "command": "closeOtherPanes" }, { "command": "closePane", "keys": "ctrl+shift+w" }, { "command": { "action": "splitPane", "split": "up" } }, - { "command": { "action": "splitPane", "split": "down" }, "keys": "alt+shift+-" }, + { "command": { "action": "splitPane", "split": "down" } }, { "command": { "action": "splitPane", "split": "left" } }, - { "command": { "action": "splitPane", "split": "right" }, "keys": "alt+shift+plus" }, + { "command": { "action": "splitPane", "split": "right" } }, + { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "down" }, "keys": "alt+shift+-" }, + { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "right" }, "keys": "alt+shift+plus" }, { "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+down" }, { "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+left" }, { "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+right" }, diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp index c38c0221c5a..f4657e82527 100644 --- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp @@ -502,7 +502,7 @@ namespace ControlUnitTests const auto& start = core->_terminal->GetSelectionAnchor(); const auto& end = core->_terminal->GetSelectionEnd(); const til::point expectedStart{ 24, 0 }; // The character after the prompt - const til::point expectedEnd{ 29, 3 }; // x = buffer.right + const til::point expectedEnd{ 21, 3 }; // x = the end of the text VERIFY_ARE_EQUAL(expectedStart, start); VERIFY_ARE_EQUAL(expectedEnd, end); } diff --git a/src/cascadia/UnitTests_SettingsModel/CommandTests.cpp b/src/cascadia/UnitTests_SettingsModel/CommandTests.cpp index 8db1141736f..b4940852c91 100644 --- a/src/cascadia/UnitTests_SettingsModel/CommandTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/CommandTests.cpp @@ -473,7 +473,8 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); - const auto& terminalArgs = realArgs.TerminalArgs(); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto& terminalArgs = realArgs.ContentArgs().try_as(); VERIFY_IS_NOT_NULL(terminalArgs); auto cmdline = terminalArgs.ToCommandline(); VERIFY_ARE_EQUAL(L"", cmdline); @@ -486,7 +487,8 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.ActionAndArgs().Action()); const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); - const auto& terminalArgs = realArgs.TerminalArgs(); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto& terminalArgs = realArgs.ContentArgs().try_as(); VERIFY_IS_NOT_NULL(terminalArgs); auto cmdline = terminalArgs.ToCommandline(); VERIFY_ARE_EQUAL(L"--profile \"foo\"", cmdline); @@ -499,7 +501,8 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); - const auto& terminalArgs = realArgs.TerminalArgs(); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto& terminalArgs = realArgs.ContentArgs().try_as(); VERIFY_IS_NOT_NULL(terminalArgs); auto cmdline = terminalArgs.ToCommandline(); VERIFY_ARE_EQUAL(L"--profile \"foo\"", cmdline); @@ -512,7 +515,8 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); - const auto& terminalArgs = realArgs.TerminalArgs(); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto& terminalArgs = realArgs.ContentArgs().try_as(); VERIFY_IS_NOT_NULL(terminalArgs); auto cmdline = terminalArgs.ToCommandline(); VERIFY_ARE_EQUAL(L"-- \"bar.exe\"", cmdline); @@ -525,7 +529,8 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); - const auto& terminalArgs = realArgs.TerminalArgs(); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto& terminalArgs = realArgs.ContentArgs().try_as(); VERIFY_IS_NOT_NULL(terminalArgs); auto cmdline = terminalArgs.ToCommandline(); Log::Comment(NoThrowString().Format( @@ -540,7 +545,8 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); - const auto& terminalArgs = realArgs.TerminalArgs(); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto& terminalArgs = realArgs.ContentArgs().try_as(); VERIFY_IS_NOT_NULL(terminalArgs); auto cmdline = terminalArgs.ToCommandline(); Log::Comment(NoThrowString().Format( @@ -555,7 +561,8 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); - const auto& terminalArgs = realArgs.TerminalArgs(); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto& terminalArgs = realArgs.ContentArgs().try_as(); VERIFY_IS_NOT_NULL(terminalArgs); auto cmdline = terminalArgs.ToCommandline(); Log::Comment(NoThrowString().Format( @@ -570,7 +577,8 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); - const auto& terminalArgs = realArgs.TerminalArgs(); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto& terminalArgs = realArgs.ContentArgs().try_as(); VERIFY_IS_NOT_NULL(terminalArgs); auto cmdline = terminalArgs.ToCommandline(); Log::Comment(NoThrowString().Format( @@ -585,7 +593,8 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); - const auto& terminalArgs = realArgs.TerminalArgs(); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto& terminalArgs = realArgs.ContentArgs().try_as(); VERIFY_IS_NOT_NULL(terminalArgs); auto cmdline = terminalArgs.ToCommandline(); Log::Comment(NoThrowString().Format( diff --git a/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp index bd17076bc94..50462a72346 100644 --- a/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp @@ -1390,11 +1390,13 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); } Log::Comment(L"Note that we're skipping ctrl+B, since that doesn't have `keys` set."); @@ -1407,11 +1409,13 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); } { const KeyChord kc{ true, false, false, false, static_cast('D'), 0 }; @@ -1421,11 +1425,13 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); } { const KeyChord kc{ true, false, false, false, static_cast('E'), 0 }; @@ -1435,11 +1441,13 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); } { const KeyChord kc{ true, false, false, false, static_cast('F'), 0 }; @@ -1449,11 +1457,13 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); } Log::Comment(L"Now verify the commands"); @@ -1478,11 +1488,13 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); } { // This was renamed to null (aka removed from the name map) in F. So this does not exist. diff --git a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp index c5f4be49045..afe22c8930c 100644 --- a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp @@ -27,6 +27,7 @@ namespace SettingsModelUnitTests TEST_METHOD(ManyKeysSameAction); TEST_METHOD(LayerKeybindings); TEST_METHOD(HashDeduplication); + TEST_METHOD(HashContentArgs); TEST_METHOD(UnbindKeybindings); TEST_METHOD(LayerScancodeKeybindings); TEST_METHOD(TestExplicitUnbind); @@ -161,6 +162,32 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(1u, actionMap->_ActionMap.size()); } + void KeyBindingsTests::HashContentArgs() + { + Log::Comment(L"These are two actions with different content args. They should have different hashes for their terminal args."); + const auto actionMap = winrt::make_self(); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", } , "keys": ["ctrl+c"] } ])"), OriginTag::None); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", "index": 0 } , "keys": ["ctrl+shift+c"] } ])"), OriginTag::None); + VERIFY_ARE_EQUAL(2u, actionMap->_ActionMap.size()); + + KeyChord ctrlC{ VirtualKeyModifiers::Control, static_cast('C'), 0 }; + KeyChord ctrlShiftC{ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast('C'), 0 }; + + auto hashFromKey = [&](auto& kc) { + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); + VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); + const auto& realArgs = actionAndArgs.Args().as(); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + return terminalArgs.Hash(); + }; + + const auto hashOne = hashFromKey(ctrlC); + const auto hashTwo = hashFromKey(ctrlShiftC); + + VERIFY_ARE_NOT_EQUAL(hashOne, hashTwo); + } + void KeyBindingsTests::UnbindKeybindings() { const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" }; @@ -318,8 +345,10 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().as(); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_NULL(realArgs.TerminalArgs().ProfileIndex()); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); } { Log::Comment(NoThrowString().Format( @@ -329,9 +358,11 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().as(); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex()); - VERIFY_ARE_EQUAL(0, realArgs.TerminalArgs().ProfileIndex().Value()); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_NOT_NULL(terminalArgs.ProfileIndex()); + VERIFY_ARE_EQUAL(0, terminalArgs.ProfileIndex().Value()); } { Log::Comment(NoThrowString().Format( @@ -342,9 +373,11 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().as(); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs().ProfileIndex()); - VERIFY_ARE_EQUAL(11, realArgs.TerminalArgs().ProfileIndex().Value()); + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_NOT_NULL(terminalArgs.ProfileIndex()); + VERIFY_ARE_EQUAL(11, terminalArgs.ProfileIndex().Value()); } { diff --git a/src/cascadia/UnitTests_SettingsModel/TerminalSettingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/TerminalSettingsTests.cpp index f45fd1624f7..051d722f941 100644 --- a/src/cascadia/UnitTests_SettingsModel/TerminalSettingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/TerminalSettingsTests.cpp @@ -320,14 +320,16 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid0, profile.Guid()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); @@ -341,15 +343,17 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", realArgs.TerminalArgs().Profile()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}", terminalArgs.Profile()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid1, profile.Guid()); VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); @@ -363,15 +367,17 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", terminalArgs.Profile()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid1, profile.Guid()); VERIFY_ARE_EQUAL(L"pwsh.exe", termSettings.Commandline()); @@ -385,15 +391,17 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Right, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile2", terminalArgs.Profile()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(profile2Guid, profile.Guid()); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); @@ -407,15 +415,17 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"foo.exe", terminalArgs.Commandline()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); // This action specified a command but no profile; it gets reassigned to the base profile VERIFY_ARE_EQUAL(settings->ProfileDefaults(), profile); @@ -430,16 +440,18 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitDirection::Down, realArgs.SplitDirection()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); - VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"profile1", terminalArgs.Profile()); + VERIFY_ARE_EQUAL(L"foo.exe", terminalArgs.Commandline()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid1, profile.Guid()); VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); @@ -452,14 +464,16 @@ namespace SettingsModelUnitTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid0, profile.Guid()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); @@ -472,15 +486,17 @@ namespace SettingsModelUnitTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_FALSE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"c:\\foo", terminalArgs.StartingDirectory()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid0, profile.Guid()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); @@ -494,16 +510,18 @@ namespace SettingsModelUnitTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_FALSE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_TRUE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"c:\\foo", terminalArgs.StartingDirectory()); + VERIFY_ARE_EQUAL(L"profile2", terminalArgs.Profile()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(profile2Guid, profile.Guid()); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); @@ -517,15 +535,17 @@ namespace SettingsModelUnitTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_FALSE(terminalArgs.TabTitle().empty()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"bar", terminalArgs.TabTitle()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid0, profile.Guid()); VERIFY_ARE_EQUAL(L"cmd.exe", termSettings.Commandline()); @@ -539,16 +559,18 @@ namespace SettingsModelUnitTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); - VERIFY_ARE_EQUAL(L"profile2", realArgs.TerminalArgs().Profile()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_TRUE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_FALSE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"bar", terminalArgs.TabTitle()); + VERIFY_ARE_EQUAL(L"profile2", terminalArgs.Profile()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(profile2Guid, profile.Guid()); VERIFY_ARE_EQUAL(L"wsl.exe", termSettings.Commandline()); @@ -562,18 +584,20 @@ namespace SettingsModelUnitTests const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"foo.exe", realArgs.TerminalArgs().Commandline()); - VERIFY_ARE_EQUAL(L"c:\\foo", realArgs.TerminalArgs().StartingDirectory()); - VERIFY_ARE_EQUAL(L"bar", realArgs.TerminalArgs().TabTitle()); - VERIFY_ARE_EQUAL(L"profile1", realArgs.TerminalArgs().Profile()); - - const auto profile{ settings->GetProfileForArgs(realArgs.TerminalArgs()) }; - const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, realArgs.TerminalArgs(), nullptr) }; + VERIFY_IS_NOT_NULL(realArgs.ContentArgs()); + const auto terminalArgs{ realArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_FALSE(terminalArgs.StartingDirectory().empty()); + VERIFY_IS_FALSE(terminalArgs.TabTitle().empty()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_ARE_EQUAL(L"foo.exe", terminalArgs.Commandline()); + VERIFY_ARE_EQUAL(L"c:\\foo", terminalArgs.StartingDirectory()); + VERIFY_ARE_EQUAL(L"bar", terminalArgs.TabTitle()); + VERIFY_ARE_EQUAL(L"profile1", terminalArgs.Profile()); + + const auto profile{ settings->GetProfileForArgs(terminalArgs) }; + const auto settingsStruct{ TerminalSettings::CreateWithNewTerminalArgs(*settings, terminalArgs, nullptr) }; const auto termSettings = settingsStruct.DefaultSettings(); VERIFY_ARE_EQUAL(guid1, profile.Guid()); VERIFY_ARE_EQUAL(L"foo.exe", termSettings.Commandline()); diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index f7948e5c59d..4c582d0f44a 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -131,7 +131,9 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final // Manually set the console into conpty mode. We're not actually going // to set up the pipes for conpty, but we want the console to behave // like it would in conpty mode. - g.EnableConptyModeForTests(std::move(vtRenderEngine)); + // + // Also, make sure to backdoor enable the resize quirk here too. + g.EnableConptyModeForTests(std::move(vtRenderEngine), true); expectedOutput.clear(); _checkConptyOutput = true; @@ -233,6 +235,11 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final TEST_METHOD(TestNoExtendedAttrsOptimization); TEST_METHOD(TestNoBackgroundAttrsOptimization); + TEST_METHOD(SimplePromptRegions); + TEST_METHOD(MultilinePromptRegions); + TEST_METHOD(ManyMultilinePromptsWithTrailingSpaces); + TEST_METHOD(ReflowPromptRegions); + private: bool _writeCallback(const char* const pch, const size_t cch); void _flushFirstFrame(); @@ -333,8 +340,9 @@ void ConptyRoundtripTests::_clearConpty() _resizeConpty(newSize.width, newSize.height); // After we resize, make sure to get the new textBuffers - return { &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetTextBuffer(), - term->_mainBuffer.get() }; + TextBuffer* hostTb = &ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetTextBuffer(); + TextBuffer* termTb = term->_mainBuffer.get(); + return { hostTb, termTb }; } void ConptyRoundtripTests::ConptyOutputTestCanary() @@ -745,6 +753,9 @@ void ConptyRoundtripTests::MoveCursorAtEOL() VERIFY_SUCCEEDED(renderer.PaintFrame()); verifyData1(termTb); + + // This test is sometimes flaky in cleanup. + expectedOutput.clear(); } void ConptyRoundtripTests::TestResizeHeight() @@ -2638,6 +2649,7 @@ void ConptyRoundtripTests::ResizeRepaintVimExeBuffer() auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& viewport) { const auto firstRow = viewport.top; const auto width = viewport.width(); + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; // First row VERIFY_IS_FALSE(tb.GetRowByOffset(firstRow).WasWrapForced()); @@ -2694,7 +2706,6 @@ void ConptyRoundtripTests::ResizeRepaintVimExeBuffer() Log::Comment(L"========== Checking the host buffer state (after) =========="); verifyBuffer(*hostTb, si.GetViewport().ToExclusive()); - Log::Comment(L"Painting the frame"); VERIFY_SUCCEEDED(renderer.PaintFrame()); @@ -4315,3 +4326,622 @@ void ConptyRoundtripTests::TestNoBackgroundAttrsOptimization() Log::Comment(L"========== Check terminal buffer =========="); verifyBuffer(*termTb); } + +#define FTCS_A L"\x1b]133;A\x1b\\" +#define FTCS_B L"\x1b]133;B\x1b\\" +#define FTCS_C L"\x1b]133;C\x1b\\" +#define FTCS_D L"\x1b]133;D\x1b\\" + +void ConptyRoundtripTests::SimplePromptRegions() +{ + Log::Comment(L"Same as the ScreenBufferTests::ComplicatedPromptRegions, but in conpty"); + + auto& g = ServiceLocator::LocateGlobals(); + auto& renderer = *g.pRender; + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& sm = si.GetStateMachine(); + + auto* hostTb = &si.GetTextBuffer(); + auto* termTb = term->_mainBuffer.get(); + + gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. + auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); + + _flushFirstFrame(); + + _checkConptyOutput = false; + + auto verifyBuffer = [&](const TextBuffer& tb) { + const auto& cursor = tb.GetCursor(); + { + const til::point expectedCursor{ 17, 4 }; + VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); + } + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; + + const auto& row0 = tb.GetRowByOffset(0); + const auto& row4 = tb.GetRowByOffset(4); + VERIFY_IS_TRUE(row0.GetScrollbarData().has_value()); + VERIFY_IS_TRUE(row4.GetScrollbarData().has_value()); + + const auto marks = tb.GetMarkExtents(); + VERIFY_ARE_EQUAL(2u, marks.size()); + + { + auto& mark = marks[0]; + const til::point expectedStart{ 0, 0 }; + const til::point expectedEnd{ 17, 0 }; + const til::point expectedOutputStart{ 24, 0 }; // `Foo-Bar` is 7 characters + const til::point expectedOutputEnd{ 13, 3 }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + + VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); + VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); + } + { + auto& mark = marks[1]; + const til::point expectedStart{ 0, 4 }; + const til::point expectedEnd{ 17, 4 }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + VERIFY_IS_FALSE(mark.commandEnd.has_value()); + VERIFY_IS_FALSE(mark.outputEnd.has_value()); + } + }; + + Log::Comment(L"========== Fill test content =========="); + + auto _writePrompt = [](StateMachine& stateMachine, const auto& path) { + // A prompt looks like: + // `PWSH C:\Windows> ` + // + // which is 17 characters for C:\Windows + stateMachine.ProcessString(FTCS_D); + stateMachine.ProcessString(FTCS_A); + stateMachine.ProcessString(L"\x1b]9;9;"); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L"\x7"); + stateMachine.ProcessString(L"PWSH "); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L"> "); + stateMachine.ProcessString(FTCS_B); + }; + + _writePrompt(sm, L"C:\\Windows"); + sm.ProcessString(L"Foo-bar"); + sm.ProcessString(FTCS_C); + sm.ProcessString(L"\r\n"); + sm.ProcessString(L"This is some text \r\n"); // y=1 + sm.ProcessString(L"with varying amounts \r\n"); // y=2 + sm.ProcessString(L"of whitespace\r\n"); // y=3 + + _writePrompt(sm, L"C:\\Windows"); // y=4 + + Log::Comment(L"========== Check host buffer =========="); + verifyBuffer(*hostTb); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + Log::Comment(L"========== Check terminal buffer =========="); + verifyBuffer(*termTb); +} + +void ConptyRoundtripTests::MultilinePromptRegions() +{ + auto& g = ServiceLocator::LocateGlobals(); + auto& renderer = *g.pRender; + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& sm = si.GetStateMachine(); + + auto* hostTb = &si.GetTextBuffer(); + auto* termTb = term->_mainBuffer.get(); + + gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. + auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); + + _flushFirstFrame(); + + _checkConptyOutput = false; + + auto bufferWidth = term->GetViewport().Width(); + + auto verifyBuffer = [&](const TextBuffer& tb) { + const auto& cursor = tb.GetCursor(); + { + const til::point expectedCursor{ 2, 6 }; + VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); + } + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; + + const auto& row0 = tb.GetRowByOffset(0); + const auto& row5 = tb.GetRowByOffset(5); + VERIFY_IS_TRUE(row0.GetScrollbarData().has_value()); + VERIFY_IS_TRUE(row5.GetScrollbarData().has_value()); + + const auto marks = tb.GetMarkExtents(); + VERIFY_ARE_EQUAL(2u, marks.size()); + + { + Log::Comment(L"Row 0"); + const auto& row = tb.GetRowByOffset(0); + const auto& attrs = row.Attributes(); + const auto& runs = attrs.runs(); + VERIFY_ARE_EQUAL(2u, runs.size()); + auto run0 = runs[0]; + auto run1 = runs[1]; + VERIFY_ARE_EQUAL(17, run0.length); + VERIFY_ARE_EQUAL(MarkKind::Prompt, run0.value.GetMarkAttributes()); + + VERIFY_ARE_EQUAL(bufferWidth - 17, run1.length); + VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); + } + { + Log::Comment(L"Row 1"); + const auto& row = tb.GetRowByOffset(1); + const auto& attrs = row.Attributes(); + const auto& runs = attrs.runs(); + VERIFY_ARE_EQUAL(3u, runs.size()); + auto run0 = runs[0]; + auto run1 = runs[1]; + auto run2 = runs[2]; + VERIFY_ARE_EQUAL(2, run0.length); + VERIFY_ARE_EQUAL(MarkKind::Prompt, run0.value.GetMarkAttributes()); + + VERIFY_ARE_EQUAL(7, run1.length); + VERIFY_ARE_EQUAL(MarkKind::Command, run1.value.GetMarkAttributes()); + + VERIFY_ARE_EQUAL(bufferWidth - 9, run2.length); + VERIFY_ARE_EQUAL(MarkKind::None, run2.value.GetMarkAttributes()); + } + { + Log::Comment(L"Row 2"); + const auto& row = tb.GetRowByOffset(2); + const auto& attrs = row.Attributes(); + const auto& runs = attrs.runs(); + VERIFY_ARE_EQUAL(2u, runs.size()); + auto run0 = runs[0]; + auto run1 = runs[1]; + VERIFY_ARE_EQUAL(22, run0.length); + VERIFY_ARE_EQUAL(MarkKind::Output, run0.value.GetMarkAttributes()); + + VERIFY_ARE_EQUAL(bufferWidth - 22, run1.length); + VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); + } + { + Log::Comment(L"Row 3"); + const auto& row = tb.GetRowByOffset(3); + const auto& attrs = row.Attributes(); + const auto& runs = attrs.runs(); + VERIFY_ARE_EQUAL(2u, runs.size()); + auto run0 = runs[0]; + auto run1 = runs[1]; + VERIFY_ARE_EQUAL(22, run0.length); + VERIFY_ARE_EQUAL(MarkKind::Output, run0.value.GetMarkAttributes()); + + VERIFY_ARE_EQUAL(bufferWidth - 22, run1.length); + VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); + } + + { + auto& mark = marks[0]; + const til::point expectedStart{ 0, 0 }; + const til::point expectedEnd{ 2, 1 }; + const til::point expectedOutputStart{ 9, 1 }; // `Foo-Bar` is 7 characters + const til::point expectedOutputEnd{ 13, 4 }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + + VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); + VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); + } + { + auto& mark = marks[1]; + const til::point expectedStart{ 0, 5 }; + const til::point expectedEnd{ 2, 6 }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + VERIFY_IS_FALSE(mark.commandEnd.has_value()); + VERIFY_IS_FALSE(mark.outputEnd.has_value()); + } + }; + + Log::Comment(L"========== Fill test content =========="); + + auto _writePrompt = [](StateMachine& stateMachine, const auto& path) { + // A prompt looks like: + // `PWSH C:\Windows >` + // `> ` + // + // which two rows. The first is 17 characters for C:\Windows + stateMachine.ProcessString(FTCS_D); + stateMachine.ProcessString(FTCS_A); + stateMachine.ProcessString(L"\x1b]9;9;"); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L"\x7"); + stateMachine.ProcessString(L"PWSH "); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L" >\r\n"); + stateMachine.ProcessString(L"> "); + stateMachine.ProcessString(FTCS_B); + }; + + _writePrompt(sm, L"C:\\Windows"); // y=0,1 + sm.ProcessString(L"Foo-bar"); + sm.ProcessString(FTCS_C); + sm.ProcessString(L"\r\n"); + sm.ProcessString(L"This is some text \r\n"); // y=2 + sm.ProcessString(L"with varying amounts \r\n"); // y=3 + sm.ProcessString(L"of whitespace\r\n"); // y=4 + + _writePrompt(sm, L"C:\\Windows"); // y=5, 6 + + Log::Comment(L"========== Check host buffer =========="); + verifyBuffer(*hostTb); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + Log::Comment(L"========== Check terminal buffer =========="); + verifyBuffer(*termTb); +} + +void ConptyRoundtripTests::ManyMultilinePromptsWithTrailingSpaces() +{ + auto& g = ServiceLocator::LocateGlobals(); + auto& renderer = *g.pRender; + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& sm = si.GetStateMachine(); + + auto* hostTb = &si.GetTextBuffer(); + auto* termTb = term->_mainBuffer.get(); + + gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. + auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); + + _flushFirstFrame(); + + _checkConptyOutput = false; + + auto bufferWidth = term->GetViewport().Width(); + + auto verifyFirstRowOfPrompt = [&](const ROW& row) { + const auto& attrs = row.Attributes(); + const auto& runs = attrs.runs(); + VERIFY_ARE_EQUAL(2u, runs.size()); + auto run0 = runs[0]; + auto run1 = runs[1]; + VERIFY_ARE_EQUAL(17, run0.length); + VERIFY_ARE_EQUAL(MarkKind::Prompt, run0.value.GetMarkAttributes()); + + VERIFY_ARE_EQUAL(bufferWidth - 17, run1.length); + VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); + }; + auto verifySecondRowOfPrompt = [&](const ROW& row, const auto expectedCommandLength) { + const auto& attrs = row.Attributes(); + const auto& runs = attrs.runs(); + VERIFY_ARE_EQUAL(3u, runs.size()); + auto run0 = runs[0]; + auto run1 = runs[1]; + auto run2 = runs[2]; + VERIFY_ARE_EQUAL(2, run0.length); + VERIFY_ARE_EQUAL(MarkKind::Prompt, run0.value.GetMarkAttributes()); + + VERIFY_ARE_EQUAL(expectedCommandLength, run1.length); + VERIFY_ARE_EQUAL(MarkKind::Command, run1.value.GetMarkAttributes()); + + VERIFY_ARE_EQUAL(bufferWidth - (2 + expectedCommandLength), run2.length); + VERIFY_ARE_EQUAL(MarkKind::None, run2.value.GetMarkAttributes()); + }; + + auto verifyBuffer = [&](const TextBuffer& tb) { + const auto& cursor = tb.GetCursor(); + { + const til::point expectedCursor{ 0, 11 }; + VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); + } + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; + + const auto marks = tb.GetMarkExtents(); + VERIFY_ARE_EQUAL(3u, marks.size()); + + Log::Comment(L"Row 0"); + verifyFirstRowOfPrompt(tb.GetRowByOffset(0)); + + Log::Comment(L"Row 1"); + verifySecondRowOfPrompt(tb.GetRowByOffset(1), 7); + + { + Log::Comment(L"Row 2"); + const auto& row = tb.GetRowByOffset(2); + const auto& attrs = row.Attributes(); + const auto& runs = attrs.runs(); + VERIFY_ARE_EQUAL(2u, runs.size()); + auto run0 = runs[0]; + auto run1 = runs[1]; + VERIFY_ARE_EQUAL(22, run0.length); + VERIFY_ARE_EQUAL(MarkKind::Output, run0.value.GetMarkAttributes()); + + VERIFY_ARE_EQUAL(bufferWidth - 22, run1.length); + VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); + } + { + Log::Comment(L"Row 3"); + const auto& row = tb.GetRowByOffset(3); + const auto& attrs = row.Attributes(); + const auto& runs = attrs.runs(); + VERIFY_ARE_EQUAL(2u, runs.size()); + auto run0 = runs[0]; + auto run1 = runs[1]; + VERIFY_ARE_EQUAL(22, run0.length); + VERIFY_ARE_EQUAL(MarkKind::Output, run0.value.GetMarkAttributes()); + + VERIFY_ARE_EQUAL(bufferWidth - 22, run1.length); + VERIFY_ARE_EQUAL(MarkKind::None, run1.value.GetMarkAttributes()); + } + + Log::Comment(L"Row 5"); + verifyFirstRowOfPrompt(tb.GetRowByOffset(5)); + + Log::Comment(L"Row 6"); + verifySecondRowOfPrompt(tb.GetRowByOffset(6), 7); + + Log::Comment(L"Row 8"); + verifyFirstRowOfPrompt(tb.GetRowByOffset(8)); + + Log::Comment(L"Row 9"); + verifySecondRowOfPrompt(tb.GetRowByOffset(9), 6); + + { + Log::Comment(L"Foo-bar mark on rows 0 & 1"); + + auto& mark = marks[0]; + const til::point expectedStart{ 0, 0 }; + const til::point expectedEnd{ 2, 1 }; + + // The command ends at {9,1} (the end of the Foo-Bar string). + // However, the first character in the output is at {0,2}. + const til::point expectedOutputStart{ 9, 1 }; // `Foo-Bar` is 7 characters + const til::point expectedOutputEnd{ 13, 4 }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + + VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); + VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); + } + { + Log::Comment(L"Boo-far mark on rows 5 & 6"); + auto& mark = marks[1]; + const til::point expectedStart{ 0, 5 }; + const til::point expectedEnd{ 2, 6 }; + const til::point expectedOutputStart{ 9, 6 }; // `Boo-far` is 7 characters + const til::point expectedOutputEnd{ 22, 7 }; + + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + + VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); + VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); + } + { + Log::Comment(L"yikes? mark on rows 8 & 9"); + auto& mark = marks[2]; + const til::point expectedStart{ 0, 8 }; + const til::point expectedEnd{ 2, 9 }; + const til::point expectedOutputStart{ 8, 9 }; // `yikes?` is 6 characters + const til::point expectedOutputEnd{ 22, 10 }; + + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + + VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); + VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); + } + }; + + Log::Comment(L"========== Fill test content =========="); + + auto writePrompt = [](StateMachine& stateMachine, const auto& path) { + // A prompt looks like: + // `PWSH C:\Windows >` + // `> ` + // + // which two rows. The first is 17 characters for C:\Windows + stateMachine.ProcessString(FTCS_D); + stateMachine.ProcessString(FTCS_A); + stateMachine.ProcessString(L"\x1b]9;9;"); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L"\x7"); + stateMachine.ProcessString(L"PWSH "); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L" >\r\n"); + stateMachine.ProcessString(L"> "); + stateMachine.ProcessString(FTCS_B); + }; + auto writeCommand = [](StateMachine& stateMachine, const auto& cmd) { + stateMachine.ProcessString(cmd); + stateMachine.ProcessString(FTCS_C); + stateMachine.ProcessString(L"\r\n"); + }; + + writePrompt(sm, L"C:\\Windows"); // y=0,1 + writeCommand(sm, L"Foo-bar"); + sm.ProcessString(L"This is some text \r\n"); // y=2 + sm.ProcessString(L"with varying amounts \r\n"); // y=3 + sm.ProcessString(L"of whitespace\r\n"); // y=4 + + writePrompt(sm, L"C:\\Windows"); // y=5, 6 + writeCommand(sm, L"Boo-far"); // y=6 + sm.ProcessString(L"This is more text \r\n"); // y=7 + + writePrompt(sm, L"C:\\Windows"); // y=8,9 + writeCommand(sm, L"yikes?"); // y=9 + sm.ProcessString(L"This is even more \r\n"); // y=10 + + Log::Comment(L"========== Check host buffer =========="); + verifyBuffer(*hostTb); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + Log::Comment(L"========== Check terminal buffer =========="); + verifyBuffer(*termTb); +} + +void ConptyRoundtripTests::ReflowPromptRegions() +{ + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") // always isolate things that resize the buffer + TEST_METHOD_PROPERTY(L"Data:dx", L"{-15, -1, 0, 1, 15}") + END_TEST_METHOD_PROPERTIES() + + INIT_TEST_PROPERTY(int, dx, L"The change in width of the buffer"); + + auto& g = ServiceLocator::LocateGlobals(); + auto& renderer = *g.pRender; + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& sm = si.GetStateMachine(); + + auto* hostTb = &si.GetTextBuffer(); + auto* termTb = term->_mainBuffer.get(); + + gci.LockConsole(); // Lock must be taken to manipulate alt/main buffer state. + auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); }); + + _flushFirstFrame(); + + _checkConptyOutput = false; + + auto originalWidth = term->GetViewport().Width(); + + auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& /*viewport*/, const bool /*isTerminal*/, const bool afterResize) { + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; + + // Just the dx=+1 case doesn't unwrap the line onto one line, but the dx=+15 case does. + const bool unwrapped = afterResize && dx > 1; + const int unwrapAdjust = unwrapped ? -1 : 0; + const auto marks = tb.GetMarkExtents(); + VERIFY_ARE_EQUAL(3u, marks.size()); + { + Log::Comment(L"Mark 0"); + + auto& mark = marks[0]; + const til::point expectedStart{ 0, 0 }; + const til::point expectedEnd{ 10, 0 }; + const til::point expectedOutputStart{ 17, 0 }; // `Foo-Bar` is 7 characters + const til::point expectedOutputEnd{ 13, 3 }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + + VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); + VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); + } + { + Log::Comment(L"Mark 1"); + + auto& mark = marks[1]; + const til::point expectedStart{ 0, 4 }; + const til::point expectedEnd{ 10, 4 }; + // {originalWidth} characters of 'F', maybe wrapped. + const til::point originalPos = til::point{ 10, 5 }; + til::point afterPos = originalPos; + // walk that original pos dx times into the actual real place in the buffer. + auto bufferViewport = tb.GetSize(); + const auto walkDir = Viewport::WalkDir{ dx < 0 ? Viewport::XWalk::LeftToRight : Viewport::XWalk::RightToLeft, + dx < 0 ? Viewport::YWalk::TopToBottom : Viewport::YWalk::BottomToTop }; + for (auto i = 0; i < std::abs(dx); i++) + { + bufferViewport.WalkInBounds(afterPos, + walkDir); + } + const auto expectedOutputStart = !afterResize ? + originalPos : // printed exactly a row, so we're exactly below the prompt + afterPos; + const til::point expectedOutputEnd{ 22, 6 + unwrapAdjust }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + + VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); + VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); + } + { + Log::Comment(L"Mark 2"); + + auto& mark = marks[2]; + const til::point expectedStart{ 0, 7 + unwrapAdjust }; + const til::point expectedEnd{ 10, 7 + unwrapAdjust }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + VERIFY_IS_TRUE(mark.commandEnd.has_value()); + VERIFY_IS_FALSE(mark.outputEnd.has_value()); + } + }; + + Log::Comment(L"========== Fill test content =========="); + + auto writePrompt = [](StateMachine& stateMachine, const auto& path) { + // A prompt looks like: + // `PWSH C:\> ` + // + // which is 10 characters for "C:\" + stateMachine.ProcessString(FTCS_D); + stateMachine.ProcessString(FTCS_A); + stateMachine.ProcessString(L"\x1b]9;9;"); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L"\x7"); + stateMachine.ProcessString(L"PWSH "); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L"> "); + stateMachine.ProcessString(FTCS_B); + }; + auto writeCommand = [](StateMachine& stateMachine, const auto& cmd) { + stateMachine.ProcessString(cmd); + stateMachine.ProcessString(FTCS_C); + stateMachine.ProcessString(L"\r\n"); + }; + + // This first prompt didn't reflow at all + writePrompt(sm, L"C:\\"); // y=0 + writeCommand(sm, L"Foo-bar"); // y=0 + sm.ProcessString(L"This is some text \r\n"); // y=1 + sm.ProcessString(L"with varying amounts \r\n"); // y=2 + sm.ProcessString(L"of whitespace\r\n"); // y=3 + + // This second one, the command does. It stretches across lines + writePrompt(sm, L"C:\\"); // y=4 + writeCommand(sm, std::wstring(originalWidth, L'F')); // y=4,5 + sm.ProcessString(L"This is more text \r\n"); // y=6 + + writePrompt(sm, L"C:\\"); // y=7 + writeCommand(sm, L"yikes?"); // y=7 + + Log::Comment(L"========== Checking the host buffer state (before) =========="); + verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), false, false); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + Log::Comment(L"========== Checking the terminal buffer state (before) =========="); + verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true, false); + + // After we resize, make sure to get the new textBuffers + std::tie(hostTb, termTb) = _performResize({ TerminalViewWidth + dx, + TerminalViewHeight }); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + Log::Comment(L"========== Checking the host buffer state (after) =========="); + verifyBuffer(*hostTb, si.GetViewport().ToExclusive(), false, true); + + Log::Comment(L"Painting the frame"); + VERIFY_SUCCEEDED(renderer.PaintFrame()); + + Log::Comment(L"========== Checking the terminal buffer state (after) =========="); + verifyBuffer(*termTb, term->_mutableViewport.ToExclusive(), true, true); +} diff --git a/src/cascadia/WindowsTerminal/WindowEmperor.cpp b/src/cascadia/WindowsTerminal/WindowEmperor.cpp index 0fc28a20433..a8f0961e420 100644 --- a/src/cascadia/WindowsTerminal/WindowEmperor.cpp +++ b/src/cascadia/WindowsTerminal/WindowEmperor.cpp @@ -504,11 +504,11 @@ void WindowEmperor::_finalizeSessionPersistence() const if (const auto tabArgs = args.try_as()) { - terminalArgs = tabArgs.TerminalArgs(); + terminalArgs = tabArgs.ContentArgs().try_as(); } else if (const auto paneArgs = args.try_as()) { - terminalArgs = paneArgs.TerminalArgs(); + terminalArgs = paneArgs.ContentArgs().try_as(); } if (terminalArgs) diff --git a/src/features.xml b/src/features.xml index 8fbf160a8c7..c2af5ed009a 100644 --- a/src/features.xml +++ b/src/features.xml @@ -89,13 +89,10 @@ Feature_ScrollbarMarks Enables the experimental scrollbar marks feature. - AlwaysDisabled - - - Dev - Canary - Preview - + AlwaysEnabled + + WindowsInbox + diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 3d6412add00..23500c2f6a8 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -446,9 +446,10 @@ void VtIo::CorkRenderer(bool corked) const noexcept // - vtRenderEngine: a VT renderer that our VtIo should use as the vt engine during these tests // Return Value: // - -void VtIo::EnableConptyModeForTests(std::unique_ptr vtRenderEngine) +void VtIo::EnableConptyModeForTests(std::unique_ptr vtRenderEngine, const bool resizeQuirk) { _initialized = true; + _resizeQuirk = resizeQuirk; _pVtRenderEngine = std::move(vtRenderEngine); } #endif diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index 7cd12036f5d..eccdb06aaac 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -43,7 +43,7 @@ namespace Microsoft::Console::VirtualTerminal void CorkRenderer(bool corked) const noexcept; #ifdef UNIT_TESTING - void EnableConptyModeForTests(std::unique_ptr vtRenderEngine); + void EnableConptyModeForTests(std::unique_ptr vtRenderEngine, const bool resizeQuirk = false); #endif bool IsResizeQuirkEnabled() const; diff --git a/src/host/getset.cpp b/src/host/getset.cpp index 3d371d4b24f..495771fdad3 100644 --- a/src/host/getset.cpp +++ b/src/host/getset.cpp @@ -859,7 +859,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont // GH#3490 - If we're in conpty mode, don't invalidate the entire // viewport. In conpty mode, the VtEngine will later decide what // part of the buffer actually needs to be re-sent to the terminal. - if (!(g.getConsoleInformation().IsInVtIoMode() && g.getConsoleInformation().GetVtIo()->IsResizeQuirkEnabled())) + if (!(g.getConsoleInformation().IsInVtIoMode() && + g.getConsoleInformation().GetVtIo()->IsResizeQuirkEnabled())) { WriteToScreen(context, context.GetViewport()); } diff --git a/src/host/globals.cpp b/src/host/globals.cpp index f36fc3686ab..d1bbb4f297b 100644 --- a/src/host/globals.cpp +++ b/src/host/globals.cpp @@ -35,9 +35,9 @@ bool Globals::IsHeadless() const // - vtRenderEngine: a VT renderer that our VtIo should use as the vt engine during these tests // Return Value: // - -void Globals::EnableConptyModeForTests(std::unique_ptr vtRenderEngine) +void Globals::EnableConptyModeForTests(std::unique_ptr vtRenderEngine, const bool resizeQuirk) { launchArgs.EnableConptyModeForTests(); - getConsoleInformation().GetVtIo()->EnableConptyModeForTests(std::move(vtRenderEngine)); + getConsoleInformation().GetVtIo()->EnableConptyModeForTests(std::move(vtRenderEngine), resizeQuirk); } #endif diff --git a/src/host/globals.h b/src/host/globals.h index 589cf166480..9cebbc0808a 100644 --- a/src/host/globals.h +++ b/src/host/globals.h @@ -77,7 +77,7 @@ class Globals bool defaultTerminalMarkerCheckRequired = false; #ifdef UNIT_TESTING - void EnableConptyModeForTests(std::unique_ptr vtRenderEngine); + void EnableConptyModeForTests(std::unique_ptr vtRenderEngine, const bool resizeQuirk = false); #endif private: diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 70b270e3cd8..044889544c7 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -424,25 +424,6 @@ void ConhostInternalGetSet::NotifyBufferRotation(const int delta) } } -void ConhostInternalGetSet::MarkPrompt(const ::ScrollMark& /*mark*/) -{ - // Not implemented for conhost. -} - -void ConhostInternalGetSet::MarkCommandStart() -{ - // Not implemented for conhost. -} - -void ConhostInternalGetSet::MarkOutputStart() -{ - // Not implemented for conhost. -} - -void ConhostInternalGetSet::MarkCommandFinish(std::optional /*error*/) -{ - // Not implemented for conhost. -} void ConhostInternalGetSet::InvokeCompletions(std::wstring_view /*menuJson*/, unsigned int /*replaceLength*/) { // Not implemented for conhost. diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index 45ea6052e21..850efe9892a 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -69,11 +69,6 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal:: void NotifyAccessibilityChange(const til::rect& changedRect) override; void NotifyBufferRotation(const int delta) override; - void MarkPrompt(const ScrollMark& mark) override; - void MarkCommandStart() override; - void MarkOutputStart() override; - void MarkCommandFinish(std::optional error) override; - void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) override; private: diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 98a6f09b2e5..7e34d2f31e6 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -264,6 +264,10 @@ class ScreenBufferTests TEST_METHOD(DelayedWrapReset); TEST_METHOD(EraseColorMode); + + TEST_METHOD(SimpleMarkCommand); + TEST_METHOD(SimpleWrappedCommand); + TEST_METHOD(SimplePromptRegions); }; void ScreenBufferTests::SingleAlternateBufferCreationTest() @@ -8404,3 +8408,175 @@ void ScreenBufferTests::EraseColorMode() VERIFY_ARE_EQUAL(expectedEraseAttr, cellData->TextAttr()); VERIFY_ARE_EQUAL(L" ", cellData->Chars()); } + +#define FTCS_A L"\x1b]133;A\x1b\\" +#define FTCS_B L"\x1b]133;B\x1b\\" +#define FTCS_C L"\x1b]133;C\x1b\\" +#define FTCS_D L"\x1b]133;D\x1b\\" + +void ScreenBufferTests::SimpleMarkCommand() +{ + auto& g = ServiceLocator::LocateGlobals(); + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& tbi = si.GetTextBuffer(); + auto& stateMachine = si.GetStateMachine(); + + stateMachine.ProcessString(L"Zero\n"); + + { + const auto currentRowOffset = tbi.GetCursor().GetPosition().y; + auto& currentRow = tbi.GetRowByOffset(currentRowOffset); + + stateMachine.ProcessString(FTCS_A L"A Prompt" FTCS_B L"my_command" FTCS_C L"\n"); + + VERIFY_IS_TRUE(currentRow.GetScrollbarData().has_value()); + } + + stateMachine.ProcessString(L"Two\n"); + VERIFY_ARE_EQUAL(L"my_command", tbi.CurrentCommand()); + + stateMachine.ProcessString(FTCS_D FTCS_A L"B Prompt" FTCS_B); + + VERIFY_ARE_EQUAL(tbi.CurrentCommand(), L""); + + stateMachine.ProcessString(L"some of a command"); + VERIFY_ARE_EQUAL(tbi.CurrentCommand(), L"some of a command"); + // Now add some color in the middle of the command: + stateMachine.ProcessString(L"\x1b[31m"); + stateMachine.ProcessString(L" & more of a command"); + + VERIFY_ARE_EQUAL(L"some of a command & more of a command", tbi.CurrentCommand()); + + std::vector expectedCommands{ L"my_command", + L"some of a command & more of a command" }; + VERIFY_ARE_EQUAL(expectedCommands, tbi.Commands()); +} + +void ScreenBufferTests::SimpleWrappedCommand() +{ + auto& g = ServiceLocator::LocateGlobals(); + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& tbi = si.GetTextBuffer(); + auto& stateMachine = si.GetStateMachine(); + + stateMachine.ProcessString(L"Zero\n"); + + const auto oneHundredZeros = std::wstring(100, L'0'); + { + const auto originalRowOffset = tbi.GetCursor().GetPosition().y; + auto& originalRow = tbi.GetRowByOffset(originalRowOffset); + stateMachine.ProcessString(FTCS_A L"A Prompt" FTCS_B); + + // This command is literally 100 '0' characters, so that we _know_ we wrapped. + stateMachine.ProcessString(oneHundredZeros); + + const auto secondRowOffset = tbi.GetCursor().GetPosition().y; + VERIFY_ARE_NOT_EQUAL(originalRowOffset, secondRowOffset); + auto& secondRow = tbi.GetRowByOffset(secondRowOffset); + + VERIFY_IS_TRUE(originalRow.GetScrollbarData().has_value()); + VERIFY_IS_FALSE(secondRow.GetScrollbarData().has_value()); + + stateMachine.ProcessString(FTCS_C L"\n"); + + VERIFY_IS_TRUE(originalRow.GetScrollbarData().has_value()); + VERIFY_IS_FALSE(secondRow.GetScrollbarData().has_value()); + } + + stateMachine.ProcessString(L"Two\n"); + VERIFY_ARE_EQUAL(oneHundredZeros, tbi.CurrentCommand()); + + stateMachine.ProcessString(FTCS_D FTCS_A L"B Prompt" FTCS_B); + + VERIFY_ARE_EQUAL(tbi.CurrentCommand(), L""); + + stateMachine.ProcessString(L"some of a command"); + VERIFY_ARE_EQUAL(tbi.CurrentCommand(), L"some of a command"); + // Now add some color in the middle of the command: + stateMachine.ProcessString(L"\x1b[31m"); + stateMachine.ProcessString(L" & more of a command"); + + VERIFY_ARE_EQUAL(L"some of a command & more of a command", tbi.CurrentCommand()); + + std::vector expectedCommands{ oneHundredZeros, + L"some of a command & more of a command" }; + VERIFY_ARE_EQUAL(expectedCommands, tbi.Commands()); +} + +static void _writePrompt(StateMachine& stateMachine, const auto& path) +{ + stateMachine.ProcessString(FTCS_D); + stateMachine.ProcessString(FTCS_A); + stateMachine.ProcessString(L"\x1b]9;9;"); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L"\x7"); + stateMachine.ProcessString(L"PWSH "); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L"> "); + stateMachine.ProcessString(FTCS_B); +} + +void ScreenBufferTests::SimplePromptRegions() +{ + auto& g = ServiceLocator::LocateGlobals(); + auto& gci = g.getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer(); + auto& tbi = si.GetTextBuffer(); + auto& stateMachine = si.GetStateMachine(); + + // A prompt looks like: + // `PWSH C:\Windows> ` + // + // which is 17 characters for C:\Windows + + _writePrompt(stateMachine, L"C:\\Windows"); + stateMachine.ProcessString(L"Foo-bar"); + stateMachine.ProcessString(FTCS_C); + stateMachine.ProcessString(L"\r\n"); + stateMachine.ProcessString(L"This is some text \r\n"); // y=1 + stateMachine.ProcessString(L"with varying amounts \r\n"); // y=2 + stateMachine.ProcessString(L"of whitespace \r\n"); // y=3 + + _writePrompt(stateMachine, L"C:\\Windows"); // y=4 + + Log::Comment(L"Check the buffer contents"); + const auto& cursor = tbi.GetCursor(); + + { + const til::point expectedCursor{ 17, 4 }; + VERIFY_ARE_EQUAL(expectedCursor, cursor.GetPosition()); + } + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; + + const auto& row0 = tbi.GetRowByOffset(0); + const auto& row4 = tbi.GetRowByOffset(4); + VERIFY_IS_TRUE(row0.GetScrollbarData().has_value()); + VERIFY_IS_TRUE(row4.GetScrollbarData().has_value()); + + const auto marks = tbi.GetMarkExtents(); + VERIFY_ARE_EQUAL(2u, marks.size()); + + { + auto& mark = marks[0]; + const til::point expectedStart{ 0, 0 }; + const til::point expectedEnd{ 17, 0 }; + const til::point expectedOutputStart{ 24, 0 }; // `Foo-Bar` is 7 characters + const til::point expectedOutputEnd{ 22, 3 }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + + VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); + VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); + } + { + auto& mark = marks[1]; + const til::point expectedStart{ 0, 4 }; + const til::point expectedEnd{ 17, 4 }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + VERIFY_IS_FALSE(mark.commandEnd.has_value()); + VERIFY_IS_FALSE(mark.outputEnd.has_value()); + } +} diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp index 648b054778b..33660d876a8 100644 --- a/src/host/ut_host/TextBufferTests.cpp +++ b/src/host/ut_host/TextBufferTests.cpp @@ -161,6 +161,8 @@ class TextBufferTests TEST_METHOD(HyperlinkTrim); TEST_METHOD(NoHyperlinkTrim); + + TEST_METHOD(ReflowPromptRegions); }; void TextBufferTests::TestBufferCreate() @@ -2852,3 +2854,135 @@ void TextBufferTests::NoHyperlinkTrim() VERIFY_ARE_EQUAL(_buffer->GetHyperlinkUriFromId(id), url); VERIFY_ARE_EQUAL(_buffer->_hyperlinkCustomIdMap[finalCustomId], id); } + +#define FTCS_A L"\x1b]133;A\x1b\\" +#define FTCS_B L"\x1b]133;B\x1b\\" +#define FTCS_C L"\x1b]133;C\x1b\\" +#define FTCS_D L"\x1b]133;D\x1b\\" +void TextBufferTests::ReflowPromptRegions() +{ + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method") // always isolate things that resize the buffer + TEST_METHOD_PROPERTY(L"Data:dx", L"{-15, -1, 0, 1, 15}") + END_TEST_METHOD_PROPERTIES() + + INIT_TEST_PROPERTY(int, dx, L"The change in width of the buffer"); + + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); + auto* tbi = &si.GetTextBuffer(); + auto& sm = si.GetStateMachine(); + const auto oldSize{ tbi->GetSize() }; + + auto verifyBuffer = [&](const TextBuffer& tb, const til::rect& /*viewport*/, const bool /*isTerminal*/, const bool afterResize) { + const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope; + + // Just the dx=+1 case doesn't unwrap the line onto one line, but the dx=+15 case does. + const bool unwrapped = afterResize && dx > 1; + const int unwrapAdjust = unwrapped ? -1 : 0; + const auto marks = tb.GetMarkExtents(); + VERIFY_ARE_EQUAL(3u, marks.size()); + { + Log::Comment(L"Mark 0"); + + auto& mark = marks[0]; + const til::point expectedStart{ 0, 0 }; + const til::point expectedEnd{ 10, 0 }; + const til::point expectedOutputStart{ 17, 0 }; // `Foo-Bar` is 7 characters + const til::point expectedOutputEnd{ 13, 3 }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + + VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); + VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); + } + { + Log::Comment(L"Mark 1"); + + auto& mark = marks[1]; + const til::point expectedStart{ 0, 4 }; + const til::point expectedEnd{ 10, 4 }; + // {originalWidth} characters of 'F', maybe wrapped. + const til::point originalPos = til::point{ 10, 5 }; + til::point afterPos = originalPos; + // walk that original pos dx times into the actual real place in the buffer. + auto bufferViewport = tb.GetSize(); + const auto walkDir = Viewport::WalkDir{ dx < 0 ? Viewport::XWalk::LeftToRight : Viewport::XWalk::RightToLeft, + dx < 0 ? Viewport::YWalk::TopToBottom : Viewport::YWalk::BottomToTop }; + for (auto i = 0; i < std::abs(dx); i++) + { + bufferViewport.WalkInBounds(afterPos, + walkDir); + } + const auto expectedOutputStart = !afterResize ? + originalPos : // printed exactly a row, so we're exactly below the prompt + afterPos; + const til::point expectedOutputEnd{ 22, 6 + unwrapAdjust }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + + VERIFY_ARE_EQUAL(expectedOutputStart, *mark.commandEnd); + VERIFY_ARE_EQUAL(expectedOutputEnd, *mark.outputEnd); + } + { + Log::Comment(L"Mark 2"); + + auto& mark = marks[2]; + const til::point expectedStart{ 0, 7 + unwrapAdjust }; + const til::point expectedEnd{ 10, 7 + unwrapAdjust }; + VERIFY_ARE_EQUAL(expectedStart, mark.start); + VERIFY_ARE_EQUAL(expectedEnd, mark.end); + VERIFY_IS_TRUE(mark.commandEnd.has_value()); + VERIFY_IS_FALSE(mark.outputEnd.has_value()); + } + }; + + Log::Comment(L"========== Fill test content =========="); + + auto writePrompt = [](StateMachine& stateMachine, const auto& path) { + // A prompt looks like: + // `PWSH C:\> ` + // + // which is 10 characters for "C:\" + stateMachine.ProcessString(FTCS_D); + stateMachine.ProcessString(FTCS_A); + stateMachine.ProcessString(L"\x1b]9;9;"); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L"\x7"); + stateMachine.ProcessString(L"PWSH "); + stateMachine.ProcessString(path); + stateMachine.ProcessString(L"> "); + stateMachine.ProcessString(FTCS_B); + }; + auto writeCommand = [](StateMachine& stateMachine, const auto& cmd) { + stateMachine.ProcessString(cmd); + stateMachine.ProcessString(FTCS_C); + stateMachine.ProcessString(L"\r\n"); + }; + + // This first prompt didn't reflow at all + writePrompt(sm, L"C:\\"); // y=0 + writeCommand(sm, L"Foo-bar"); // y=0 + sm.ProcessString(L"This is some text \r\n"); // y=1 + sm.ProcessString(L"with varying amounts \r\n"); // y=2 + sm.ProcessString(L"of whitespace\r\n"); // y=3 + + // This second one, the command does. It stretches across lines + writePrompt(sm, L"C:\\"); // y=4 + writeCommand(sm, std::wstring(oldSize.Width(), L'F')); // y=4,5 + sm.ProcessString(L"This is more text \r\n"); // y=6 + + writePrompt(sm, L"C:\\"); // y=7 + writeCommand(sm, L"yikes?"); // y=7 + + Log::Comment(L"========== Checking the buffer state (before) =========="); + verifyBuffer(*tbi, si.GetViewport().ToExclusive(), false, false); + + // After we resize, make sure to get the new textBuffers + til::size newSize{ oldSize.Width() + dx, oldSize.Height() }; + auto newBuffer = std::make_unique(newSize, TextAttribute{ 0x7 }, 0, false, _renderer); + TextBuffer::Reflow(*tbi, *newBuffer); + + Log::Comment(L"========== Checking the host buffer state (after) =========="); + verifyBuffer(*newBuffer, si.GetViewport().ToExclusive(), false, true); +} diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h index 511e4d4d95e..4dc9f6b21e8 100644 --- a/src/inc/LibraryIncludes.h +++ b/src/inc/LibraryIncludes.h @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -44,7 +43,6 @@ #include #include #include -#include #include #include #include diff --git a/src/inc/til/bitmap.h b/src/inc/til/bitmap.h index 3934af878cc..686369ade95 100644 --- a/src/inc/til/bitmap.h +++ b/src/inc/til/bitmap.h @@ -466,17 +466,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" std::wstring to_string() const { - std::wstringstream wss; - wss << std::endl - << L"Bitmap of size " << _sz.to_string() << " contains the following dirty regions:" << std::endl; - wss << L"Runs:" << std::endl; - + auto str = fmt::format(FMT_COMPILE(L"Bitmap of size {} contains the following dirty regions:\nRuns:"), _sz.to_string()); for (auto& item : *this) { - wss << L"\t- " << item.to_string() << std::endl; + fmt::format_to(std::back_inserter(str), FMT_COMPILE(L"\n\t- {}"), item.to_string()); } - - return wss.str(); + return str; } private: diff --git a/src/inc/til/color.h b/src/inc/til/color.h index cfeee352288..81dd2d9b6b1 100644 --- a/src/inc/til/color.h +++ b/src/inc/til/color.h @@ -198,24 +198,17 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" std::wstring to_string() const { - std::wstringstream wss; - wss << L"Color " << ToHexString(false); - return wss.str(); + return ToHexString(false); } + std::wstring ToHexString(const bool omitAlpha = false) const { - std::wstringstream wss; - wss << L"#" << std::uppercase << std::setfill(L'0') << std::hex; - // Force the compiler to promote from byte to int. Without it, the - // stringstream will try to write the components as chars - wss << std::setw(2) << static_cast(r); - wss << std::setw(2) << static_cast(g); - wss << std::setw(2) << static_cast(b); - if (!omitAlpha) + auto str = fmt::format(FMT_COMPILE(L"#{:02X}{:02X}{:02X}{:02X}"), r, g, b, a); + if (omitAlpha) { - wss << std::setw(2) << static_cast(a); + str.resize(7); } - return wss.str(); + return str; } }; #pragma warning(pop) diff --git a/src/inc/til/env.h b/src/inc/til/env.h index 33d7fa70e40..e41ae645dd5 100644 --- a/src/inc/til/env.h +++ b/src/inc/til/env.h @@ -86,7 +86,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" inline constexpr wil::zwstring_view system_env_var_root{ LR"(SYSTEM\CurrentControlSet\Control\Session Manager\Environment)" }; inline constexpr wil::zwstring_view user_env_var_root{ LR"(Environment)" }; inline constexpr wil::zwstring_view user_volatile_env_var_root{ LR"(Volatile Environment)" }; - inline constexpr wil::zwstring_view user_volatile_session_env_var_root_pattern{ LR"(Volatile Environment\{0:d})" }; }; }; @@ -472,7 +471,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" // not processing autoexec.bat get_vars_from_registry(HKEY_CURRENT_USER, til::details::vars::reg::user_env_var_root); get_vars_from_registry(HKEY_CURRENT_USER, til::details::vars::reg::user_volatile_env_var_root); - get_vars_from_registry(HKEY_CURRENT_USER, fmt::format(til::details::vars::reg::user_volatile_session_env_var_root_pattern, NtCurrentTeb()->ProcessEnvironmentBlock->SessionId)); + get_vars_from_registry(HKEY_CURRENT_USER, fmt::format(FMT_COMPILE(LR"(Volatile Environment\{})"), NtCurrentTeb()->ProcessEnvironmentBlock->SessionId)); } std::wstring to_string() const diff --git a/src/interactivity/win32/uiaTextRange.cpp b/src/interactivity/win32/uiaTextRange.cpp index d060b3e3985..eb78d090cfc 100644 --- a/src/interactivity/win32/uiaTextRange.cpp +++ b/src/interactivity/win32/uiaTextRange.cpp @@ -60,14 +60,6 @@ IFACEMETHODIMP UiaTextRange::Clone(_Outptr_result_maybenull_ ITextRangeProvider* *ppRetVal = nullptr; RETURN_IF_FAILED(MakeAndInitialize(ppRetVal, *this)); -#if defined(_DEBUG) && defined(UiaTextRangeBase_DEBUG_MSGS) - OutputDebugString(L"Clone\n"); - std::wstringstream ss; - ss << _id << L" cloned to " << (static_cast(*ppRetVal))->_id; - std::wstring str = ss.str(); - OutputDebugString(str.c_str()); - OutputDebugString(L"\n"); -#endif // TODO GitHub #1914: Re-attach Tracing to UIA Tree // tracing /*ApiMsgClone apiMsg; diff --git a/src/interactivity/win32/windowproc.cpp b/src/interactivity/win32/windowproc.cpp index 3e2225e182f..28202eebc9f 100644 --- a/src/interactivity/win32/windowproc.cpp +++ b/src/interactivity/win32/windowproc.cpp @@ -721,9 +721,7 @@ using namespace Microsoft::Console::Types; { try { - std::wstringstream wss; - wss << std::setfill(L'0') << std::setw(8) << wParam; - auto wstr = wss.str(); + const auto wstr = fmt::format(FMT_COMPILE(L"{:08d}"), wParam); LoadKeyboardLayout(wstr.c_str(), KLF_ACTIVATE); } catch (...) diff --git a/src/renderer/atlas/pch.h b/src/renderer/atlas/pch.h index 0192f1995e9..36a2f2143c5 100644 --- a/src/renderer/atlas/pch.h +++ b/src/renderer/atlas/pch.h @@ -50,5 +50,9 @@ #include #pragma warning(pop) +// {fmt}, a C++20-compatible formatting library +#include +#include + #include #include diff --git a/src/terminal/adapter/ITerminalApi.hpp b/src/terminal/adapter/ITerminalApi.hpp index 3375a968b04..24125780f4b 100644 --- a/src/terminal/adapter/ITerminalApi.hpp +++ b/src/terminal/adapter/ITerminalApi.hpp @@ -81,11 +81,6 @@ namespace Microsoft::Console::VirtualTerminal virtual void NotifyAccessibilityChange(const til::rect& changedRect) = 0; virtual void NotifyBufferRotation(const int delta) = 0; - virtual void MarkPrompt(const ScrollMark& mark) = 0; - virtual void MarkCommandStart() = 0; - virtual void MarkOutputStart() = 0; - virtual void MarkCommandFinish(std::optional error) = 0; - virtual void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) = 0; }; } diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index dfe540936d0..cdf50148ef3 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -3671,7 +3671,7 @@ bool AdaptDispatch::DoConEmuAction(const std::wstring_view string) // This seems like basically the same as 133;B - the end of the prompt, the start of the commandline. else if (subParam == 12) { - _api.MarkCommandStart(); + _api.GetTextBuffer().StartCommand(); return true; } @@ -3690,12 +3690,12 @@ bool AdaptDispatch::DoConEmuAction(const std::wstring_view string) // - false in conhost, true for the SetMark action, otherwise false. bool AdaptDispatch::DoITerm2Action(const std::wstring_view string) { - // This is not implemented in conhost. - if (_api.IsConsolePty()) + const auto isConPty = _api.IsConsolePty(); + if (isConPty) { - // Flush the frame manually, to make sure marks end up on the right line, like the alt buffer sequence. + // Flush the frame manually, to make sure marks end up on the right + // line, like the alt buffer sequence. _renderer.TriggerFlush(false); - return false; } if constexpr (!Feature_ScrollbarMarks::IsEnabled()) @@ -3712,14 +3712,14 @@ bool AdaptDispatch::DoITerm2Action(const std::wstring_view string) const auto action = til::at(parts, 0); + bool handled = false; if (action == L"SetMark") { - ScrollMark mark; - mark.category = MarkCategory::Prompt; - _api.MarkPrompt(mark); - return true; + _api.GetTextBuffer().StartPrompt(); + handled = true; } - return false; + + return handled && !isConPty; } // Method Description: @@ -3734,12 +3734,12 @@ bool AdaptDispatch::DoITerm2Action(const std::wstring_view string) // - false in conhost, true for the SetMark action, otherwise false. bool AdaptDispatch::DoFinalTermAction(const std::wstring_view string) { - // This is not implemented in conhost. - if (_api.IsConsolePty()) + const auto isConPty = _api.IsConsolePty(); + if (isConPty) { - // Flush the frame manually, to make sure marks end up on the right line, like the alt buffer sequence. + // Flush the frame manually, to make sure marks end up on the right + // line, like the alt buffer sequence. _renderer.TriggerFlush(false); - return false; } if constexpr (!Feature_ScrollbarMarks::IsEnabled()) @@ -3753,7 +3753,7 @@ bool AdaptDispatch::DoFinalTermAction(const std::wstring_view string) { return false; } - + bool handled = false; const auto action = til::at(parts, 0); if (action.size() == 1) { @@ -3761,21 +3761,21 @@ bool AdaptDispatch::DoFinalTermAction(const std::wstring_view string) { case L'A': // FTCS_PROMPT { - // Simply just mark this line as a prompt line. - ScrollMark mark; - mark.category = MarkCategory::Prompt; - _api.MarkPrompt(mark); - return true; + _api.GetTextBuffer().StartPrompt(); + handled = true; + break; } case L'B': // FTCS_COMMAND_START { - _api.MarkCommandStart(); - return true; + _api.GetTextBuffer().StartCommand(); + handled = true; + break; } case L'C': // FTCS_COMMAND_EXECUTED { - _api.MarkOutputStart(); - return true; + _api.GetTextBuffer().StartOutput(); + handled = true; + break; } case L'D': // FTCS_COMMAND_FINISHED { @@ -3793,12 +3793,15 @@ bool AdaptDispatch::DoFinalTermAction(const std::wstring_view string) error = Utils::StringToUint(errorString, parsedError) ? parsedError : UINT_MAX; } - _api.MarkCommandFinish(error); - return true; + + _api.GetTextBuffer().EndCurrentCommand(error); + + handled = true; + break; } default: { - return false; + handled = false; } } } @@ -3807,7 +3810,7 @@ bool AdaptDispatch::DoFinalTermAction(const std::wstring_view string) // simple state machine here to track the most recently emitted mark from // this set of sequences, and which sequence was emitted last, so we can // modify the state of that mark as we go. - return false; + return handled && !isConPty; } // Method Description: // - Performs a VsCode action diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index da8b7a35d3a..c4012933dc1 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -215,22 +215,6 @@ class TestGetSet final : public ITerminalApi Log::Comment(L"NotifyBufferRotation MOCK called..."); } - void MarkPrompt(const ScrollMark& /*mark*/) override - { - Log::Comment(L"MarkPrompt MOCK called..."); - } - void MarkCommandStart() override - { - Log::Comment(L"MarkCommandStart MOCK called..."); - } - void MarkOutputStart() override - { - Log::Comment(L"MarkOutputStart MOCK called..."); - } - void MarkCommandFinish(std::optional /*error*/) override - { - Log::Comment(L"MarkCommandFinish MOCK called..."); - } void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) override { Log::Comment(L"InvokeCompletions MOCK called..."); diff --git a/src/types/UiaTracing.cpp b/src/types/UiaTracing.cpp index dae05a073b9..80bc99d1668 100644 --- a/src/types/UiaTracing.cpp +++ b/src/types/UiaTracing.cpp @@ -65,29 +65,14 @@ UiaTracing::~UiaTracing() noexcept std::wstring UiaTracing::_getValue(const ScreenInfoUiaProviderBase& siup) noexcept { - std::wstringstream stream; - stream << "_id: " << siup.GetId(); - return stream.str(); + return fmt::format(FMT_COMPILE(L"_id:{}"), siup.GetId()); } std::wstring UiaTracing::_getValue(const UiaTextRangeBase& utr) noexcept -try { const auto start = utr.GetEndpoint(TextPatternRangeEndpoint_Start); const auto end = utr.GetEndpoint(TextPatternRangeEndpoint_End); - - std::wstringstream stream; - stream << " _id: " << utr.GetId(); - stream << " _start: { " << start.x << ", " << start.y << " }"; - stream << " _end: { " << end.x << ", " << end.y << " }"; - stream << " _degenerate: " << utr.IsDegenerate(); - stream << " _wordDelimiters: " << utr._wordDelimiters; - stream << " content: " << utr._getTextValue(); - return stream.str(); -} -catch (...) -{ - return {}; + return fmt::format(FMT_COMPILE(L"_id:{} _start:{},{} _end:{},{} _degenerate:{} _wordDelimiters:{} content:{}"), utr.GetId(), start.x, start.y, end.x, end.y, utr.IsDegenerate(), utr._wordDelimiters, utr._getTextValue()); } std::wstring UiaTracing::_getValue(const TextPatternRangeEndpoint endpoint) noexcept @@ -485,7 +470,7 @@ void UiaTracing::TextProvider::get_ProviderOptions(const ScreenInfoUiaProviderBa EnsureRegistration(); if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) { - auto getOptions = [options]() { + static constexpr auto getOptions = [](ProviderOptions options) { switch (options) { case ProviderOptions_ServerSideProvider: @@ -499,7 +484,7 @@ void UiaTracing::TextProvider::get_ProviderOptions(const ScreenInfoUiaProviderBa g_UiaProviderTraceProvider, "ScreenInfoUiaProvider::get_ProviderOptions", TraceLoggingValue(_getValue(siup).c_str(), "base"), - TraceLoggingValue(getOptions(), "providerOptions"), + TraceLoggingValue(getOptions(options), "providerOptions"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); } @@ -510,7 +495,7 @@ void UiaTracing::TextProvider::GetPatternProvider(const ScreenInfoUiaProviderBas EnsureRegistration(); if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) { - auto getPattern = [patternId]() { + static constexpr auto getPattern = [](PATTERNID patternId) { switch (patternId) { case UIA_TextPatternId: @@ -524,7 +509,7 @@ void UiaTracing::TextProvider::GetPatternProvider(const ScreenInfoUiaProviderBas g_UiaProviderTraceProvider, "ScreenInfoUiaProvider::get_ProviderOptions", TraceLoggingValue(_getValue(siup).c_str(), "base"), - TraceLoggingValue(getPattern(), "patternId"), + TraceLoggingValue(getPattern(patternId), "patternId"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); } @@ -535,7 +520,7 @@ void UiaTracing::TextProvider::GetPropertyValue(const ScreenInfoUiaProviderBase& EnsureRegistration(); if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) { - auto getProperty = [propertyId]() { + static constexpr auto getProperty = [](PROPERTYID propertyId) { switch (propertyId) { case UIA_ControlTypePropertyId: @@ -565,7 +550,7 @@ void UiaTracing::TextProvider::GetPropertyValue(const ScreenInfoUiaProviderBase& g_UiaProviderTraceProvider, "ScreenInfoUiaProvider::GetPropertyValue", TraceLoggingValue(_getValue(siup).c_str(), "base"), - TraceLoggingValue(getProperty(), "propertyId"), + TraceLoggingValue(getProperty(propertyId), "propertyId"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); } @@ -677,17 +662,15 @@ void UiaTracing::TextProvider::RangeFromPoint(const ScreenInfoUiaProviderBase& s EnsureRegistration(); if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) { - auto getPoint = [point]() { - std::wstringstream stream; - stream << "{ " << point.x << ", " << point.y << " }"; - return stream.str(); + static constexpr auto getPoint = [](const UiaPoint& point) { + return fmt::format(FMT_COMPILE(L"{},{}"), (float)point.x, (float)point.y); }; TraceLoggingWrite( g_UiaProviderTraceProvider, "ScreenInfoUiaProvider::RangeFromPoint", TraceLoggingValue(_getValue(siup).c_str(), "base"), - TraceLoggingValue(getPoint().c_str(), "uiaPoint"), + TraceLoggingValue(getPoint(point).c_str(), "uiaPoint"), TraceLoggingValue(_getValue(result).c_str(), "result (utr)"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); @@ -714,7 +697,7 @@ void UiaTracing::TextProvider::get_SupportedTextSelection(const ScreenInfoUiaPro EnsureRegistration(); if (TraceLoggingProviderEnabled(g_UiaProviderTraceProvider, WINEVENT_LEVEL_VERBOSE, TIL_KEYWORD_TRACE)) { - auto getResult = [result]() { + static constexpr auto getResult = [](SupportedTextSelection result) { switch (result) { case SupportedTextSelection_Single: @@ -728,7 +711,7 @@ void UiaTracing::TextProvider::get_SupportedTextSelection(const ScreenInfoUiaPro g_UiaProviderTraceProvider, "ScreenInfoUiaProvider::get_SupportedTextSelection", TraceLoggingValue(_getValue(siup).c_str(), "base"), - TraceLoggingValue(getResult(), "result"), + TraceLoggingValue(getResult(result), "result"), TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE), TraceLoggingKeyword(TIL_KEYWORD_TRACE)); } diff --git a/src/types/utils.cpp b/src/types/utils.cpp index 92ce0a0ed09..53cc3d9505b 100644 --- a/src/types/utils.cpp +++ b/src/types/utils.cpp @@ -93,14 +93,7 @@ GUID Utils::CreateGuid() // - a string representation of the color std::string Utils::ColorToHexString(const til::color color) { - std::stringstream ss; - ss << "#" << std::uppercase << std::setfill('0') << std::hex; - // Force the compiler to promote from byte to int. Without it, the - // stringstream will try to write the components as chars - ss << std::setw(2) << static_cast(color.r); - ss << std::setw(2) << static_cast(color.g); - ss << std::setw(2) << static_cast(color.b); - return ss.str(); + return fmt::format(FMT_COMPILE("#{:02X}{:02X}{:02X}"), color.r, color.g, color.b); } // Function Description: @@ -809,7 +802,7 @@ std::tuple Utils::MangleStartingDirectoryForWSL(std: } return { - fmt::format(LR"("{}" --cd "{}" {})", executablePath.native(), mangledDirectory, arguments), + fmt::format(FMT_COMPILE(LR"("{}" --cd "{}" {})"), executablePath.native(), mangledDirectory, arguments), std::wstring{} }; }