mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-21 06:18:34 +00:00
Compare commits
9 Commits
dev/migrie
...
release-0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95cca5470e | ||
|
|
795fb69865 | ||
|
|
90157e30d3 | ||
|
|
a925ecea72 | ||
|
|
3b53014d90 | ||
|
|
ea690e1c09 | ||
|
|
0c77366b23 | ||
|
|
5ecff02a63 | ||
|
|
545c43ec0f |
@@ -1243,26 +1243,6 @@ std::string TextBuffer::GenHTML(const TextAndColor& rows, const int fontHeightPo
|
|||||||
|
|
||||||
for (size_t col = 0; col < rows.text.at(row).length(); col++)
|
for (size_t col = 0; col < rows.text.at(row).length(); col++)
|
||||||
{
|
{
|
||||||
// do not include \r nor \n as they don't have attributes
|
|
||||||
// and are not HTML friendly. For line break use '<BR>' instead.
|
|
||||||
const bool isLastCharInRow =
|
|
||||||
col == rows.text.at(row).length() - 1 ||
|
|
||||||
rows.text.at(row).at(col + 1) == '\r' ||
|
|
||||||
rows.text.at(row).at(col + 1) == '\n';
|
|
||||||
|
|
||||||
bool colorChanged = false;
|
|
||||||
if (!fgColor.has_value() || rows.FgAttr.at(row).at(col) != fgColor.value())
|
|
||||||
{
|
|
||||||
fgColor = rows.FgAttr.at(row).at(col);
|
|
||||||
colorChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bkColor.has_value() || rows.BkAttr.at(row).at(col) != bkColor.value())
|
|
||||||
{
|
|
||||||
bkColor = rows.BkAttr.at(row).at(col);
|
|
||||||
colorChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto writeAccumulatedChars = [&](bool includeCurrent) {
|
const auto writeAccumulatedChars = [&](bool includeCurrent) {
|
||||||
if (col >= startOffset)
|
if (col >= startOffset)
|
||||||
{
|
{
|
||||||
@@ -1289,6 +1269,27 @@ std::string TextBuffer::GenHTML(const TextAndColor& rows, const int fontHeightPo
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (rows.text.at(row).at(col) == '\r' || rows.text.at(row).at(col) == '\n')
|
||||||
|
{
|
||||||
|
// do not include \r nor \n as they don't have color attributes
|
||||||
|
// and are not HTML friendly. For line break use '<BR>' instead.
|
||||||
|
writeAccumulatedChars(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool colorChanged = false;
|
||||||
|
if (!fgColor.has_value() || rows.FgAttr.at(row).at(col) != fgColor.value())
|
||||||
|
{
|
||||||
|
fgColor = rows.FgAttr.at(row).at(col);
|
||||||
|
colorChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bkColor.has_value() || rows.BkAttr.at(row).at(col) != bkColor.value())
|
||||||
|
{
|
||||||
|
bkColor = rows.BkAttr.at(row).at(col);
|
||||||
|
colorChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (colorChanged)
|
if (colorChanged)
|
||||||
{
|
{
|
||||||
writeAccumulatedChars(false);
|
writeAccumulatedChars(false);
|
||||||
@@ -1310,10 +1311,10 @@ std::string TextBuffer::GenHTML(const TextAndColor& rows, const int fontHeightPo
|
|||||||
|
|
||||||
hasWrittenAnyText = true;
|
hasWrittenAnyText = true;
|
||||||
|
|
||||||
if (isLastCharInRow)
|
// if this is the last character in the row, flush the whole row
|
||||||
|
if (col == rows.text.at(row).length() - 1)
|
||||||
{
|
{
|
||||||
writeAccumulatedChars(true);
|
writeAccumulatedChars(true);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1430,24 +1431,6 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
|
|||||||
|
|
||||||
for (size_t col = 0; col < rows.text.at(row).length(); ++col)
|
for (size_t col = 0; col < rows.text.at(row).length(); ++col)
|
||||||
{
|
{
|
||||||
const bool isLastCharInRow =
|
|
||||||
col == rows.text.at(row).length() - 1 ||
|
|
||||||
rows.text.at(row).at(col + 1) == '\r' ||
|
|
||||||
rows.text.at(row).at(col + 1) == '\n';
|
|
||||||
|
|
||||||
bool colorChanged = false;
|
|
||||||
if (!fgColor.has_value() || rows.FgAttr.at(row).at(col) != fgColor.value())
|
|
||||||
{
|
|
||||||
fgColor = rows.FgAttr.at(row).at(col);
|
|
||||||
colorChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bkColor.has_value() || rows.BkAttr.at(row).at(col) != bkColor.value())
|
|
||||||
{
|
|
||||||
bkColor = rows.BkAttr.at(row).at(col);
|
|
||||||
colorChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto writeAccumulatedChars = [&](bool includeCurrent) {
|
const auto writeAccumulatedChars = [&](bool includeCurrent) {
|
||||||
if (col >= startOffset)
|
if (col >= startOffset)
|
||||||
{
|
{
|
||||||
@@ -1470,6 +1453,27 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (rows.text.at(row).at(col) == '\r' || rows.text.at(row).at(col) == '\n')
|
||||||
|
{
|
||||||
|
// do not include \r nor \n as they don't have color attributes.
|
||||||
|
// For line break use \line instead.
|
||||||
|
writeAccumulatedChars(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool colorChanged = false;
|
||||||
|
if (!fgColor.has_value() || rows.FgAttr.at(row).at(col) != fgColor.value())
|
||||||
|
{
|
||||||
|
fgColor = rows.FgAttr.at(row).at(col);
|
||||||
|
colorChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bkColor.has_value() || rows.BkAttr.at(row).at(col) != bkColor.value())
|
||||||
|
{
|
||||||
|
bkColor = rows.BkAttr.at(row).at(col);
|
||||||
|
colorChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (colorChanged)
|
if (colorChanged)
|
||||||
{
|
{
|
||||||
writeAccumulatedChars(false);
|
writeAccumulatedChars(false);
|
||||||
@@ -1513,10 +1517,10 @@ std::string TextBuffer::GenRTF(const TextAndColor& rows, const int fontHeightPoi
|
|||||||
<< " ";
|
<< " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLastCharInRow)
|
// if this is the last character in the row, flush the whole row
|
||||||
|
if (col == rows.text.at(row).length() - 1)
|
||||||
{
|
{
|
||||||
writeAccumulatedChars(true);
|
writeAccumulatedChars(true);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,12 @@ namespace winrt
|
|||||||
// !!! IMPORTANT !!!
|
// !!! IMPORTANT !!!
|
||||||
// Make sure that these keys are in the same order as the
|
// Make sure that these keys are in the same order as the
|
||||||
// SettingsLoadWarnings/Errors enum is!
|
// SettingsLoadWarnings/Errors enum is!
|
||||||
static const std::array<std::wstring_view, 3> settingsLoadWarningsLabels {
|
static const std::array<std::wstring_view, 5> settingsLoadWarningsLabels {
|
||||||
USES_RESOURCE(L"MissingDefaultProfileText"),
|
USES_RESOURCE(L"MissingDefaultProfileText"),
|
||||||
USES_RESOURCE(L"DuplicateProfileText"),
|
USES_RESOURCE(L"DuplicateProfileText"),
|
||||||
USES_RESOURCE(L"UnknownColorSchemeText")
|
USES_RESOURCE(L"UnknownColorSchemeText"),
|
||||||
|
USES_RESOURCE(L"InvalidBackgroundImage"),
|
||||||
|
USES_RESOURCE(L"InvalidIcon")
|
||||||
};
|
};
|
||||||
static const std::array<std::wstring_view, 2> settingsLoadErrorsLabels {
|
static const std::array<std::wstring_view, 2> settingsLoadErrorsLabels {
|
||||||
USES_RESOURCE(L"NoProfilesText"),
|
USES_RESOURCE(L"NoProfilesText"),
|
||||||
|
|||||||
@@ -198,6 +198,10 @@ void CascadiaSettings::_ValidateSettings()
|
|||||||
// just use the hardcoded defaults
|
// just use the hardcoded defaults
|
||||||
_ValidateAllSchemesExist();
|
_ValidateAllSchemesExist();
|
||||||
|
|
||||||
|
// Ensure all profile's with specified images resources have valid file path.
|
||||||
|
// This validates icons and background images.
|
||||||
|
_ValidateMediaResources();
|
||||||
|
|
||||||
// TODO:GH#2548 ensure there's at least one key bound. Display a warning if
|
// TODO:GH#2548 ensure there's at least one key bound. Display a warning if
|
||||||
// there's _NO_ keys bound to any actions. That's highly irregular, and
|
// there's _NO_ keys bound to any actions. That's highly irregular, and
|
||||||
// likely an indication of an error somehow.
|
// likely an indication of an error somehow.
|
||||||
@@ -424,6 +428,64 @@ void CascadiaSettings::_ValidateAllSchemesExist()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Ensures that all specified images resources (icons and background images) are valid URIs.
|
||||||
|
// This does not verify that the icon or background image files are encoded as an image.
|
||||||
|
// Arguments:
|
||||||
|
// - <none>
|
||||||
|
// Return Value:
|
||||||
|
// - <none>
|
||||||
|
// - Appends a SettingsLoadWarnings::InvalidBackgroundImage to our list of warnings if
|
||||||
|
// we find any invalid background images.
|
||||||
|
// - Appends a SettingsLoadWarnings::InvalidIconImage to our list of warnings if
|
||||||
|
// we find any invalid icon images.
|
||||||
|
void CascadiaSettings::_ValidateMediaResources()
|
||||||
|
{
|
||||||
|
bool invalidBackground{ false };
|
||||||
|
bool invalidIcon{ false };
|
||||||
|
|
||||||
|
for (auto& profile : _profiles)
|
||||||
|
{
|
||||||
|
if (profile.HasBackgroundImage())
|
||||||
|
{
|
||||||
|
// Attempt to convert the path to a URI, the ctor will throw if it's invalid/unparseable.
|
||||||
|
// This covers file paths on the machine, app data, URLs, and other resource paths.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
winrt::Windows::Foundation::Uri imagePath{ profile.GetExpandedBackgroundImagePath() };
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
profile.ResetBackgroundImagePath();
|
||||||
|
invalidBackground = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile.HasIcon())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
winrt::Windows::Foundation::Uri imagePath{ profile.GetExpandedIconPath() };
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
profile.ResetIconPath();
|
||||||
|
invalidIcon = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalidBackground)
|
||||||
|
{
|
||||||
|
_warnings.push_back(::TerminalApp::SettingsLoadWarnings::InvalidBackgroundImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalidIcon)
|
||||||
|
{
|
||||||
|
_warnings.push_back(::TerminalApp::SettingsLoadWarnings::InvalidIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
// - Create a TerminalSettings object for the provided newTerminalArgs. We'll
|
// - Create a TerminalSettings object for the provided newTerminalArgs. We'll
|
||||||
// use the newTerminalArgs to look up the profile that should be used to
|
// use the newTerminalArgs to look up the profile that should be used to
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ private:
|
|||||||
void _ReorderProfilesToMatchUserSettingsOrder();
|
void _ReorderProfilesToMatchUserSettingsOrder();
|
||||||
void _RemoveHiddenProfiles();
|
void _RemoveHiddenProfiles();
|
||||||
void _ValidateAllSchemesExist();
|
void _ValidateAllSchemesExist();
|
||||||
|
void _ValidateMediaResources();
|
||||||
|
|
||||||
friend class TerminalAppLocalTests::SettingsTests;
|
friend class TerminalAppLocalTests::SettingsTests;
|
||||||
friend class TerminalAppLocalTests::ProfileTests;
|
friend class TerminalAppLocalTests::ProfileTests;
|
||||||
|
|||||||
@@ -358,6 +358,25 @@ void Pane::Close()
|
|||||||
_ClosedHandlers(nullptr, nullptr);
|
_ClosedHandlers(nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Prepare this pane to be removed from the UI hierarchy by closing all controls
|
||||||
|
// and connections beneath it.
|
||||||
|
void Pane::Shutdown()
|
||||||
|
{
|
||||||
|
// Lock the create/close lock so that another operation won't concurrently
|
||||||
|
// modify our tree
|
||||||
|
std::unique_lock lock{ _createCloseLock };
|
||||||
|
if (_IsLeaf())
|
||||||
|
{
|
||||||
|
_control.Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_firstChild->Shutdown();
|
||||||
|
_secondChild->Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
// - Get the root UIElement of this pane. There may be a single TermControl as a
|
// - Get the root UIElement of this pane. There may be a single TermControl as a
|
||||||
// child, or an entire tree of grids and panes as children of this element.
|
// child, or an entire tree of grids and panes as children of this element.
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ public:
|
|||||||
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||||
|
|
||||||
|
void Shutdown();
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||||
|
|||||||
@@ -847,6 +847,14 @@ void Profile::SetIconPath(std::wstring_view path)
|
|||||||
_icon.emplace(path);
|
_icon.emplace(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Resets the std::optional holding the icon file path string.
|
||||||
|
// HasIcon() will return false after the execution of this function.
|
||||||
|
void Profile::ResetIconPath()
|
||||||
|
{
|
||||||
|
_icon.reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
// - Returns this profile's icon path, if one is set. Otherwise returns the
|
// - Returns this profile's icon path, if one is set. Otherwise returns the
|
||||||
// empty string. This method will expand any environment variables in the
|
// empty string. This method will expand any environment variables in the
|
||||||
@@ -880,6 +888,14 @@ winrt::hstring Profile::GetExpandedBackgroundImagePath() const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Resets the std::optional holding the background image file path string.
|
||||||
|
// HasBackgroundImage() will return false after the execution of this function.
|
||||||
|
void Profile::ResetBackgroundImagePath()
|
||||||
|
{
|
||||||
|
_backgroundImage.reset();
|
||||||
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
// - Returns the name of this profile.
|
// - Returns the name of this profile.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
|
|||||||
@@ -89,9 +89,11 @@ public:
|
|||||||
bool HasIcon() const noexcept;
|
bool HasIcon() const noexcept;
|
||||||
winrt::hstring GetExpandedIconPath() const;
|
winrt::hstring GetExpandedIconPath() const;
|
||||||
void SetIconPath(std::wstring_view path);
|
void SetIconPath(std::wstring_view path);
|
||||||
|
void ResetIconPath();
|
||||||
|
|
||||||
bool HasBackgroundImage() const noexcept;
|
bool HasBackgroundImage() const noexcept;
|
||||||
winrt::hstring GetExpandedBackgroundImagePath() const;
|
winrt::hstring GetExpandedBackgroundImagePath() const;
|
||||||
|
void ResetBackgroundImagePath();
|
||||||
|
|
||||||
CloseOnExitMode GetCloseOnExitMode() const noexcept;
|
CloseOnExitMode GetCloseOnExitMode() const noexcept;
|
||||||
bool GetSuppressApplicationTitle() const noexcept;
|
bool GetSuppressApplicationTitle() const noexcept;
|
||||||
|
|||||||
@@ -210,4 +210,10 @@ Temporarily using the Windows Terminal default settings.
|
|||||||
<data name="CloseWindowWarningTitle" xml:space="preserve">
|
<data name="CloseWindowWarningTitle" xml:space="preserve">
|
||||||
<value>Do you want to close all tabs?</value>
|
<value>Do you want to close all tabs?</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||||
|
<value>Found a profile with an invalid "backgroundImage". Defaulting that profile to have no background image. Make sure that when setting a "backgroundImage", the value is a valid file path to an image.</value>
|
||||||
|
</data>
|
||||||
|
<data name="InvalidIcon" xml:space="preserve">
|
||||||
|
<value>Found a profile with an invalid "icon". Defaulting that profile to have no icon. Make sure that when setting an "icon", the value is a valid file path to an image.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -297,6 +297,13 @@ void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction)
|
|||||||
_rootPane->NavigateFocus(direction);
|
_rootPane->NavigateFocus(direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections.
|
||||||
|
void Tab::Shutdown()
|
||||||
|
{
|
||||||
|
_rootPane->Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
// - Closes the currently focused pane in this tab. If it's the last pane in
|
// - Closes the currently focused pane in this tab. If it's the last pane in
|
||||||
// this tab, our Closed event will be fired (at a later time) for anyone
|
// this tab, our Closed event will be fired (at a later time) for anyone
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public:
|
|||||||
winrt::hstring GetActiveTitle() const;
|
winrt::hstring GetActiveTitle() const;
|
||||||
void SetTabText(const winrt::hstring& text);
|
void SetTabText(const winrt::hstring& text);
|
||||||
|
|
||||||
|
void Shutdown();
|
||||||
void ClosePane();
|
void ClosePane();
|
||||||
|
|
||||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||||
|
|||||||
@@ -757,16 +757,20 @@ namespace winrt::TerminalApp::implementation
|
|||||||
// - tabIndex: the index of the tab to be removed
|
// - tabIndex: the index of the tab to be removed
|
||||||
void TerminalPage::_RemoveTabViewItemByIndex(uint32_t tabIndex)
|
void TerminalPage::_RemoveTabViewItemByIndex(uint32_t tabIndex)
|
||||||
{
|
{
|
||||||
|
// Removing the tab from the collection should destroy its control and disconnect its connection,
|
||||||
|
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
|
||||||
|
auto iterator = _tabs.begin() + tabIndex;
|
||||||
|
(*iterator)->Shutdown();
|
||||||
|
|
||||||
|
_tabs.erase(iterator);
|
||||||
|
_tabView.TabItems().RemoveAt(tabIndex);
|
||||||
|
|
||||||
// To close the window here, we need to close the hosting window.
|
// To close the window here, we need to close the hosting window.
|
||||||
if (_tabs.size() == 1)
|
if (_tabs.size() == 0)
|
||||||
{
|
{
|
||||||
_lastTabClosedHandlers(*this, nullptr);
|
_lastTabClosedHandlers(*this, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removing the tab from the collection will destroy its control and disconnect its connection.
|
|
||||||
_tabs.erase(_tabs.begin() + tabIndex);
|
|
||||||
_tabView.TabItems().RemoveAt(tabIndex);
|
|
||||||
|
|
||||||
auto focusedTabIndex = _GetFocusedTabIndex();
|
auto focusedTabIndex = _GetFocusedTabIndex();
|
||||||
if (gsl::narrow_cast<int>(tabIndex) == focusedTabIndex)
|
if (gsl::narrow_cast<int>(tabIndex) == focusedTabIndex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ namespace TerminalApp
|
|||||||
{
|
{
|
||||||
MissingDefaultProfile = 0,
|
MissingDefaultProfile = 0,
|
||||||
DuplicateProfile = 1,
|
DuplicateProfile = 1,
|
||||||
UnknownColorScheme = 2
|
UnknownColorScheme = 2,
|
||||||
|
InvalidBackgroundImage = 3,
|
||||||
|
InvalidIcon = 4
|
||||||
};
|
};
|
||||||
|
|
||||||
// SettingsLoadWarnings are scenarios where the settings had invalid state
|
// SettingsLoadWarnings are scenarios where the settings had invalid state
|
||||||
|
|||||||
@@ -59,23 +59,32 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||||||
// set the input scope to Text because this control is for any text.
|
// set the input scope to Text because this control is for any text.
|
||||||
_editContext.InputScope(Core::CoreTextInputScope::Text);
|
_editContext.InputScope(Core::CoreTextInputScope::Text);
|
||||||
|
|
||||||
_editContext.TextRequested({ this, &TSFInputControl::_textRequestedHandler });
|
_textRequestedRevoker = _editContext.TextRequested(winrt::auto_revoke, { this, &TSFInputControl::_textRequestedHandler });
|
||||||
|
|
||||||
_editContext.SelectionRequested({ this, &TSFInputControl::_selectionRequestedHandler });
|
_selectionRequestedRevoker = _editContext.SelectionRequested(winrt::auto_revoke, { this, &TSFInputControl::_selectionRequestedHandler });
|
||||||
|
|
||||||
_editContext.FocusRemoved({ this, &TSFInputControl::_focusRemovedHandler });
|
_focusRemovedRevoker = _editContext.FocusRemoved(winrt::auto_revoke, { this, &TSFInputControl::_focusRemovedHandler });
|
||||||
|
|
||||||
_editContext.TextUpdating({ this, &TSFInputControl::_textUpdatingHandler });
|
_textUpdatingRevoker = _editContext.TextUpdating(winrt::auto_revoke, { this, &TSFInputControl::_textUpdatingHandler });
|
||||||
|
|
||||||
_editContext.SelectionUpdating({ this, &TSFInputControl::_selectionUpdatingHandler });
|
_selectionUpdatingRevoker = _editContext.SelectionUpdating(winrt::auto_revoke, { this, &TSFInputControl::_selectionUpdatingHandler });
|
||||||
|
|
||||||
_editContext.FormatUpdating({ this, &TSFInputControl::_formatUpdatingHandler });
|
_formatUpdatingRevoker = _editContext.FormatUpdating(winrt::auto_revoke, { this, &TSFInputControl::_formatUpdatingHandler });
|
||||||
|
|
||||||
_editContext.LayoutRequested({ this, &TSFInputControl::_layoutRequestedHandler });
|
_layoutRequestedRevoker = _editContext.LayoutRequested(winrt::auto_revoke, { this, &TSFInputControl::_layoutRequestedHandler });
|
||||||
|
|
||||||
_editContext.CompositionStarted({ this, &TSFInputControl::_compositionStartedHandler });
|
_compositionStartedRevoker = _editContext.CompositionStarted(winrt::auto_revoke, { this, &TSFInputControl::_compositionStartedHandler });
|
||||||
|
|
||||||
_editContext.CompositionCompleted({ this, &TSFInputControl::_compositionCompletedHandler });
|
_compositionCompletedRevoker = _editContext.CompositionCompleted(winrt::auto_revoke, { this, &TSFInputControl::_compositionCompletedHandler });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method Description:
|
||||||
|
// - Prepares this TSFInputControl to be removed from the UI hierarchy.
|
||||||
|
void TSFInputControl::Close()
|
||||||
|
{
|
||||||
|
// Explicitly disconnect the LayoutRequested handler -- it can cause problems during application teardown.
|
||||||
|
// See GH#4159 for more info.
|
||||||
|
_layoutRequestedRevoker.revoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method Description:
|
// Method Description:
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||||||
void NotifyFocusEnter();
|
void NotifyFocusEnter();
|
||||||
void NotifyFocusLeave();
|
void NotifyFocusLeave();
|
||||||
|
|
||||||
|
void Close();
|
||||||
|
|
||||||
static void OnCompositionChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);
|
static void OnCompositionChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);
|
||||||
|
|
||||||
// -------------------------------- WinRT Events ---------------------------------
|
// -------------------------------- WinRT Events ---------------------------------
|
||||||
@@ -55,6 +57,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||||||
void _textUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, winrt::Windows::UI::Text::Core::CoreTextTextUpdatingEventArgs const& args);
|
void _textUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, winrt::Windows::UI::Text::Core::CoreTextTextUpdatingEventArgs const& args);
|
||||||
void _formatUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, winrt::Windows::UI::Text::Core::CoreTextFormatUpdatingEventArgs const& args);
|
void _formatUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, winrt::Windows::UI::Text::Core::CoreTextFormatUpdatingEventArgs const& args);
|
||||||
|
|
||||||
|
winrt::Windows::UI::Text::Core::CoreTextEditContext::TextRequested_revoker _textRequestedRevoker;
|
||||||
|
winrt::Windows::UI::Text::Core::CoreTextEditContext::SelectionRequested_revoker _selectionRequestedRevoker;
|
||||||
|
winrt::Windows::UI::Text::Core::CoreTextEditContext::FocusRemoved_revoker _focusRemovedRevoker;
|
||||||
|
winrt::Windows::UI::Text::Core::CoreTextEditContext::TextUpdating_revoker _textUpdatingRevoker;
|
||||||
|
winrt::Windows::UI::Text::Core::CoreTextEditContext::SelectionUpdating_revoker _selectionUpdatingRevoker;
|
||||||
|
winrt::Windows::UI::Text::Core::CoreTextEditContext::FormatUpdating_revoker _formatUpdatingRevoker;
|
||||||
|
winrt::Windows::UI::Text::Core::CoreTextEditContext::LayoutRequested_revoker _layoutRequestedRevoker;
|
||||||
|
winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionStarted_revoker _compositionStartedRevoker;
|
||||||
|
winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionCompleted_revoker _compositionCompletedRevoker;
|
||||||
|
|
||||||
Windows::UI::Xaml::Controls::Canvas _canvas;
|
Windows::UI::Xaml::Controls::Canvas _canvas;
|
||||||
Windows::UI::Xaml::Controls::TextBlock _textBlock;
|
Windows::UI::Xaml::Controls::TextBlock _textBlock;
|
||||||
|
|
||||||
|
|||||||
@@ -27,5 +27,7 @@ namespace Microsoft.Terminal.TerminalControl
|
|||||||
|
|
||||||
void NotifyFocusEnter();
|
void NotifyFocusEnter();
|
||||||
void NotifyFocusLeave();
|
void NotifyFocusLeave();
|
||||||
|
|
||||||
|
void Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1725,6 +1725,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||||||
_connection.TerminalOutput(_connectionOutputEventToken);
|
_connection.TerminalOutput(_connectionOutputEventToken);
|
||||||
_connectionStateChangedRevoker.revoke();
|
_connectionStateChangedRevoker.revoke();
|
||||||
|
|
||||||
|
_tsfInputControl.Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
|
||||||
|
|
||||||
if (auto localConnection{ std::exchange(_connection, nullptr) })
|
if (auto localConnection{ std::exchange(_connection, nullptr) })
|
||||||
{
|
{
|
||||||
localConnection.Close();
|
localConnection.Close();
|
||||||
|
|||||||
@@ -456,20 +456,33 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView)
|
|||||||
{
|
{
|
||||||
// TODO: MSFT 21006766
|
// TODO: MSFT 21006766
|
||||||
// This is not great but I need it demoable. Fix by making a buffer stream writer.
|
// This is not great but I need it demoable. Fix by making a buffer stream writer.
|
||||||
if (wch >= 0xD800 && wch <= 0xDFFF)
|
//
|
||||||
|
// If wch is a surrogate character we need to read 2 code units
|
||||||
|
// from the stringView to form a single code point.
|
||||||
|
const auto isSurrogate = wch >= 0xD800 && wch <= 0xDFFF;
|
||||||
|
const auto view = stringView.substr(i, isSurrogate ? 2 : 1);
|
||||||
|
const OutputCellIterator it{ view, _buffer->GetCurrentAttributes() };
|
||||||
|
const auto end = _buffer->Write(it);
|
||||||
|
const auto cellDistance = end.GetCellDistance(it);
|
||||||
|
const auto inputDistance = end.GetInputDistance(it);
|
||||||
|
|
||||||
|
if (inputDistance > 0)
|
||||||
{
|
{
|
||||||
const OutputCellIterator it{ stringView.substr(i, 2), _buffer->GetCurrentAttributes() };
|
// If "wch" was a surrogate character, we just consumed 2 code units above.
|
||||||
const auto end = _buffer->Write(it);
|
// -> Increment "i" by 1 in that case and thus by 2 in total in this iteration.
|
||||||
const auto cellDistance = end.GetCellDistance(it);
|
|
||||||
i += cellDistance - 1;
|
|
||||||
proposedCursorPosition.X += gsl::narrow<SHORT>(cellDistance);
|
proposedCursorPosition.X += gsl::narrow<SHORT>(cellDistance);
|
||||||
|
i += inputDistance - 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const OutputCellIterator it{ stringView.substr(i, 1), _buffer->GetCurrentAttributes() };
|
// If _WriteBuffer() is called with a consecutive string longer than the viewport/buffer width
|
||||||
const auto end = _buffer->Write(it);
|
// the call to _buffer->Write() will refuse to write anything on the current line.
|
||||||
const auto cellDistance = end.GetCellDistance(it);
|
// GetInputDistance() thus returns 0, which would in turn cause i to be
|
||||||
proposedCursorPosition.X += gsl::narrow<SHORT>(cellDistance);
|
// decremented by 1 below and force the outer loop to loop forever.
|
||||||
|
// This if() basically behaves as if "\r\n" had been encountered above and retries the write.
|
||||||
|
// With well behaving shells during normal operation this safeguard should normally not be encountered.
|
||||||
|
proposedCursorPosition.X = 0;
|
||||||
|
proposedCursorPosition.Y++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
using namespace winrt::Microsoft::Terminal::Settings;
|
using namespace winrt::Microsoft::Terminal::Settings;
|
||||||
using namespace Microsoft::Terminal::Core;
|
using namespace Microsoft::Terminal::Core;
|
||||||
|
|
||||||
|
using namespace WEX::Logging;
|
||||||
|
using namespace WEX::TestExecution;
|
||||||
|
|
||||||
namespace TerminalCoreUnitTests
|
namespace TerminalCoreUnitTests
|
||||||
{
|
{
|
||||||
#define WCS(x) WCSHELPER(x)
|
#define WCS(x) WCSHELPER(x)
|
||||||
@@ -37,5 +40,74 @@ namespace TerminalCoreUnitTests
|
|||||||
VERIFY_IS_FALSE(term.SetColorTableEntry(256, 100));
|
VERIFY_IS_FALSE(term.SetColorTableEntry(256, 100));
|
||||||
VERIFY_IS_FALSE(term.SetColorTableEntry(512, 100));
|
VERIFY_IS_FALSE(term.SetColorTableEntry(512, 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Terminal::_WriteBuffer used to enter infinite loops under certain conditions.
|
||||||
|
// This test ensures that Terminal::_WriteBuffer doesn't get stuck when
|
||||||
|
// PrintString() is called with more code units than the buffer width.
|
||||||
|
TEST_METHOD(PrintStringOfSurrogatePairs)
|
||||||
|
{
|
||||||
|
DummyRenderTarget renderTarget;
|
||||||
|
Terminal term;
|
||||||
|
term.Create({ 100, 100 }, 3, renderTarget);
|
||||||
|
|
||||||
|
std::wstring text;
|
||||||
|
text.reserve(600);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 100; ++i)
|
||||||
|
{
|
||||||
|
text.append(L"𐐌𐐜𐐬");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Baton
|
||||||
|
{
|
||||||
|
HANDLE done;
|
||||||
|
std::wstring text;
|
||||||
|
Terminal* pTerm;
|
||||||
|
} baton{
|
||||||
|
CreateEventW(nullptr, TRUE, FALSE, L"done signal"),
|
||||||
|
text,
|
||||||
|
&term,
|
||||||
|
};
|
||||||
|
|
||||||
|
Log::Comment(L"Launching thread to write data.");
|
||||||
|
const auto thread = CreateThread(
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
[](LPVOID data) -> DWORD {
|
||||||
|
const Baton& baton = *reinterpret_cast<Baton*>(data);
|
||||||
|
Log::Comment(L"Writing data.");
|
||||||
|
baton.pTerm->PrintString(baton.text);
|
||||||
|
Log::Comment(L"Setting event.");
|
||||||
|
SetEvent(baton.done);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
(LPVOID)&baton,
|
||||||
|
0,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
Log::Comment(L"Waiting for the write.");
|
||||||
|
switch (WaitForSingleObject(baton.done, 2000))
|
||||||
|
{
|
||||||
|
case WAIT_OBJECT_0:
|
||||||
|
Log::Comment(L"Didn't get stuck. Success.");
|
||||||
|
break;
|
||||||
|
case WAIT_TIMEOUT:
|
||||||
|
Log::Comment(L"Wait timed out. It got stuck.");
|
||||||
|
Log::Result(WEX::Logging::TestResults::Failed);
|
||||||
|
break;
|
||||||
|
case WAIT_FAILED:
|
||||||
|
Log::Comment(L"Wait failed for some reason. We didn't expect this.");
|
||||||
|
Log::Result(WEX::Logging::TestResults::Failed);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log::Comment(L"Wait return code that no one expected. Fail.");
|
||||||
|
Log::Result(WEX::Logging::TestResults::Failed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
TerminateThread(thread, 0);
|
||||||
|
CloseHandle(baton.done);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1407,67 +1407,6 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
|
|||||||
return Status;
|
return Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
|
||||||
// - A private API call for moving the cursor vertically in the buffer. This is
|
|
||||||
// because the vertical cursor movements in VT are constrained by the
|
|
||||||
// scroll margins, while the absolute positioning is not.
|
|
||||||
// Parameters:
|
|
||||||
// - screenInfo - a reference to the screen buffer we should move the cursor for
|
|
||||||
// - lines - The number of lines to move the cursor. Up is negative, down positive.
|
|
||||||
// Return value:
|
|
||||||
// - S_OK if handled successfully. Otherwise an appropriate HRESULT for failing to clamp.
|
|
||||||
[[nodiscard]] HRESULT DoSrvMoveCursorVertically(SCREEN_INFORMATION& screenInfo, const short lines)
|
|
||||||
{
|
|
||||||
auto& cursor = screenInfo.GetActiveBuffer().GetTextBuffer().GetCursor();
|
|
||||||
COORD clampedPos = { cursor.GetPosition().X, cursor.GetPosition().Y + lines };
|
|
||||||
|
|
||||||
// Make sure the cursor doesn't move outside the viewport.
|
|
||||||
screenInfo.GetViewport().Clamp(clampedPos);
|
|
||||||
|
|
||||||
// Make sure the cursor stays inside the margins
|
|
||||||
if (screenInfo.AreMarginsSet())
|
|
||||||
{
|
|
||||||
const auto margins = screenInfo.GetAbsoluteScrollMargins().ToInclusive();
|
|
||||||
|
|
||||||
const auto cursorY = cursor.GetPosition().Y;
|
|
||||||
|
|
||||||
const auto lo = margins.Top;
|
|
||||||
const auto hi = margins.Bottom;
|
|
||||||
|
|
||||||
// See microsoft/terminal#2929 - If the cursor is _below_ the top
|
|
||||||
// margin, it should stay below the top margin. If it's _above_ the
|
|
||||||
// bottom, it should stay above the bottom. Cursor movements that stay
|
|
||||||
// outside the margins shouldn't necessarily be affected. For example,
|
|
||||||
// moving up while below the bottom margin shouldn't just jump straight
|
|
||||||
// to the bottom margin. See
|
|
||||||
// ScreenBufferTests::CursorUpDownOutsideMargins for a test of that
|
|
||||||
// behavior.
|
|
||||||
const bool cursorBelowTop = cursorY >= lo;
|
|
||||||
const bool cursorAboveBottom = cursorY <= hi;
|
|
||||||
|
|
||||||
if (cursorBelowTop)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
clampedPos.Y = std::max(clampedPos.Y, lo);
|
|
||||||
}
|
|
||||||
CATCH_RETURN();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cursorAboveBottom)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
clampedPos.Y = std::min(clampedPos.Y, hi);
|
|
||||||
}
|
|
||||||
CATCH_RETURN();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor.SetPosition(clampedPos);
|
|
||||||
|
|
||||||
return S_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
// - A private API call for swapping to the alternate screen buffer. In virtual terminals, there exists both a "main"
|
// - A private API call for swapping to the alternate screen buffer. In virtual terminals, there exists both a "main"
|
||||||
// screen buffer and an alternate. ASBSET creates a new alternate, and switches to it. If there is an already
|
// screen buffer and an alternate. ASBSET creates a new alternate, and switches to it. If there is an already
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
|
|||||||
|
|
||||||
[[nodiscard]] NTSTATUS DoSrvPrivateSetScrollingRegion(SCREEN_INFORMATION& screenInfo, const SMALL_RECT& scrollMargins);
|
[[nodiscard]] NTSTATUS DoSrvPrivateSetScrollingRegion(SCREEN_INFORMATION& screenInfo, const SMALL_RECT& scrollMargins);
|
||||||
[[nodiscard]] NTSTATUS DoSrvPrivateReverseLineFeed(SCREEN_INFORMATION& screenInfo);
|
[[nodiscard]] NTSTATUS DoSrvPrivateReverseLineFeed(SCREEN_INFORMATION& screenInfo);
|
||||||
[[nodiscard]] HRESULT DoSrvMoveCursorVertically(SCREEN_INFORMATION& screenInfo, const short lines);
|
|
||||||
|
|
||||||
[[nodiscard]] NTSTATUS DoSrvPrivateUseAlternateScreenBuffer(SCREEN_INFORMATION& screenInfo);
|
[[nodiscard]] NTSTATUS DoSrvPrivateUseAlternateScreenBuffer(SCREEN_INFORMATION& screenInfo);
|
||||||
void DoSrvPrivateUseMainScreenBuffer(SCREEN_INFORMATION& screenInfo);
|
void DoSrvPrivateUseMainScreenBuffer(SCREEN_INFORMATION& screenInfo);
|
||||||
|
|||||||
@@ -397,23 +397,6 @@ bool ConhostInternalGetSet::PrivateReverseLineFeed()
|
|||||||
return NT_SUCCESS(DoSrvPrivateReverseLineFeed(_io.GetActiveOutputBuffer()));
|
return NT_SUCCESS(DoSrvPrivateReverseLineFeed(_io.GetActiveOutputBuffer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
|
||||||
// - Connects the MoveCursorVertically call directly into our Driver Message servicing call inside Conhost.exe
|
|
||||||
// MoveCursorVertically is an internal-only "API" call that the vt commands can execute,
|
|
||||||
// but it is not represented as a function call on out public API surface.
|
|
||||||
// Return Value:
|
|
||||||
// - true if successful (see DoSrvMoveCursorVertically). false otherwise.
|
|
||||||
bool ConhostInternalGetSet::MoveCursorVertically(const ptrdiff_t lines)
|
|
||||||
{
|
|
||||||
SHORT l;
|
|
||||||
if (FAILED(PtrdiffTToShort(lines, &l)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SUCCEEDED(DoSrvMoveCursorVertically(_io.GetActiveOutputBuffer(), l));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
// - Connects the SetConsoleTitleW API call directly into our Driver Message servicing call inside Conhost.exe
|
// - Connects the SetConsoleTitleW API call directly into our Driver Message servicing call inside Conhost.exe
|
||||||
// Arguments:
|
// Arguments:
|
||||||
|
|||||||
@@ -100,8 +100,6 @@ public:
|
|||||||
|
|
||||||
bool PrivateReverseLineFeed() override;
|
bool PrivateReverseLineFeed() override;
|
||||||
|
|
||||||
bool MoveCursorVertically(const ptrdiff_t lines) override;
|
|
||||||
|
|
||||||
bool SetConsoleTitleW(const std::wstring_view title) override;
|
bool SetConsoleTitleW(const std::wstring_view title) override;
|
||||||
|
|
||||||
bool PrivateUseAlternateScreenBuffer() override;
|
bool PrivateUseAlternateScreenBuffer() override;
|
||||||
|
|||||||
@@ -199,6 +199,8 @@ class ScreenBufferTests
|
|||||||
TEST_METHOD(CursorUpDownOutsideMargins);
|
TEST_METHOD(CursorUpDownOutsideMargins);
|
||||||
TEST_METHOD(CursorUpDownExactlyAtMargins);
|
TEST_METHOD(CursorUpDownExactlyAtMargins);
|
||||||
|
|
||||||
|
TEST_METHOD(CursorNextPreviousLine);
|
||||||
|
|
||||||
TEST_METHOD(CursorSaveRestore);
|
TEST_METHOD(CursorSaveRestore);
|
||||||
|
|
||||||
TEST_METHOD(ScreenAlignmentPattern);
|
TEST_METHOD(ScreenAlignmentPattern);
|
||||||
@@ -5295,6 +5297,70 @@ void ScreenBufferTests::CursorUpDownExactlyAtMargins()
|
|||||||
stateMachine.ProcessString(L"\x1b[r");
|
stateMachine.ProcessString(L"\x1b[r");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScreenBufferTests::CursorNextPreviousLine()
|
||||||
|
{
|
||||||
|
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||||
|
auto& si = gci.GetActiveOutputBuffer();
|
||||||
|
auto& stateMachine = si.GetStateMachine();
|
||||||
|
auto& cursor = si.GetTextBuffer().GetCursor();
|
||||||
|
|
||||||
|
Log::Comment(L"Make sure the viewport is at 0,0");
|
||||||
|
VERIFY_SUCCEEDED(si.SetViewportOrigin(true, COORD({ 0, 0 }), true));
|
||||||
|
|
||||||
|
Log::Comment(L"CNL without margins");
|
||||||
|
// Starting from column 20 of line 10.
|
||||||
|
cursor.SetPosition(COORD{ 20, 10 });
|
||||||
|
// Move down 5 lines (CNL).
|
||||||
|
stateMachine.ProcessString(L"\x1b[5E");
|
||||||
|
// We should end up in column 0 of line 15.
|
||||||
|
VERIFY_ARE_EQUAL(COORD({ 0, 15 }), cursor.GetPosition());
|
||||||
|
|
||||||
|
Log::Comment(L"CPL without margins");
|
||||||
|
// Starting from column 20 of line 10.
|
||||||
|
cursor.SetPosition(COORD{ 20, 10 });
|
||||||
|
// Move up 5 lines (CPL).
|
||||||
|
stateMachine.ProcessString(L"\x1b[5F");
|
||||||
|
// We should end up in column 0 of line 5.
|
||||||
|
VERIFY_ARE_EQUAL(COORD({ 0, 5 }), cursor.GetPosition());
|
||||||
|
|
||||||
|
// Set the margins to 8:12 (9:13 in VT coordinates).
|
||||||
|
stateMachine.ProcessString(L"\x1b[9;13r");
|
||||||
|
// Make sure we clear the margins on exit so they can't break other tests.
|
||||||
|
auto clearMargins = wil::scope_exit([&] { stateMachine.ProcessString(L"\x1b[r"); });
|
||||||
|
|
||||||
|
Log::Comment(L"CNL inside margins");
|
||||||
|
// Starting from column 20 of line 10.
|
||||||
|
cursor.SetPosition(COORD{ 20, 10 });
|
||||||
|
// Move down 5 lines (CNL).
|
||||||
|
stateMachine.ProcessString(L"\x1b[5E");
|
||||||
|
// We should stop on line 12, the bottom margin.
|
||||||
|
VERIFY_ARE_EQUAL(COORD({ 0, 12 }), cursor.GetPosition());
|
||||||
|
|
||||||
|
Log::Comment(L"CPL inside margins");
|
||||||
|
// Starting from column 20 of line 10.
|
||||||
|
cursor.SetPosition(COORD{ 20, 10 });
|
||||||
|
// Move up 5 lines (CPL).
|
||||||
|
stateMachine.ProcessString(L"\x1b[5F");
|
||||||
|
// We should stop on line 8, the top margin.
|
||||||
|
VERIFY_ARE_EQUAL(COORD({ 0, 8 }), cursor.GetPosition());
|
||||||
|
|
||||||
|
Log::Comment(L"CNL below bottom");
|
||||||
|
// Starting from column 20 of line 13 (1 below bottom margin).
|
||||||
|
cursor.SetPosition(COORD{ 20, 13 });
|
||||||
|
// Move down 5 lines (CNL).
|
||||||
|
stateMachine.ProcessString(L"\x1b[5E");
|
||||||
|
// We should end up in column 0 of line 18.
|
||||||
|
VERIFY_ARE_EQUAL(COORD({ 0, 18 }), cursor.GetPosition());
|
||||||
|
|
||||||
|
Log::Comment(L"CPL above top margin");
|
||||||
|
// Starting from column 20 of line 7 (1 above top margin).
|
||||||
|
cursor.SetPosition(COORD{ 20, 7 });
|
||||||
|
// Move up 5 lines (CPL).
|
||||||
|
stateMachine.ProcessString(L"\x1b[5F");
|
||||||
|
// We should end up in column 0 of line 2.
|
||||||
|
VERIFY_ARE_EQUAL(COORD({ 0, 2 }), cursor.GetPosition());
|
||||||
|
}
|
||||||
|
|
||||||
void ScreenBufferTests::CursorSaveRestore()
|
void ScreenBufferTests::CursorSaveRestore()
|
||||||
{
|
{
|
||||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||||
|
|||||||
@@ -404,8 +404,30 @@ CustomTextLayout::CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory
|
|||||||
// Offsets is how far to move the origin (in pixels) from where it is
|
// Offsets is how far to move the origin (in pixels) from where it is
|
||||||
auto& offset = _glyphOffsets.at(i);
|
auto& offset = _glyphOffsets.at(i);
|
||||||
|
|
||||||
// Get how many columns we expected the glyph to have and mutiply into pixels.
|
// Get how many columns we expected the glyph to have and multiply into pixels.
|
||||||
const auto columns = _textClusterColumns.at(i);
|
UINT16 columns = 0;
|
||||||
|
{
|
||||||
|
// Because of typographic features such as ligatures, it is well possible for a glyph to represent
|
||||||
|
// multiple code points. Previous calls to IDWriteTextAnalyzer::GetGlyphs stores the mapping
|
||||||
|
// information between code points and glyphs in _glyphClusters.
|
||||||
|
// To properly allocate the columns for such glyphs, we need to find all characters that this glyph
|
||||||
|
// is representing and add column counts for all the characters together.
|
||||||
|
|
||||||
|
// Find the range for current glyph run in _glyphClusters.
|
||||||
|
const auto runStartIterator = _glyphClusters.begin() + run.textStart;
|
||||||
|
const auto runEndIterator = _glyphClusters.begin() + run.textStart + run.textLength;
|
||||||
|
|
||||||
|
// Find the range of characters that the current glyph is representing.
|
||||||
|
const auto firstIterator = std::find(runStartIterator, runEndIterator, i - run.glyphStart);
|
||||||
|
const auto lastIterator = std::find(runStartIterator, runEndIterator, i - run.glyphStart + 1);
|
||||||
|
|
||||||
|
// Add all allocated column counts together.
|
||||||
|
for (auto j = firstIterator; j < lastIterator; j++)
|
||||||
|
{
|
||||||
|
const auto charIndex = std::distance(_glyphClusters.begin(), j);
|
||||||
|
columns += _textClusterColumns.at(charIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
const auto advanceExpected = static_cast<float>(columns * _width);
|
const auto advanceExpected = static_cast<float>(columns * _width);
|
||||||
|
|
||||||
// If what we expect is bigger than what we have... pad it out.
|
// If what we expect is bigger than what we have... pad it out.
|
||||||
|
|||||||
@@ -81,137 +81,6 @@ void AdaptDispatch::PrintString(const std::wstring_view string)
|
|||||||
CATCH_LOG();
|
CATCH_LOG();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
|
||||||
// - Generalizes cursor movement for up/down/left/right and next/previous line.
|
|
||||||
// Arguments:
|
|
||||||
// - dir - Specific direction to move
|
|
||||||
// - distance - Magnitude of the move
|
|
||||||
// Return Value:
|
|
||||||
// - True if handled successfully. False otherwise.
|
|
||||||
bool AdaptDispatch::_CursorMovement(const CursorDirection dir, const size_t distance) const
|
|
||||||
{
|
|
||||||
// First retrieve some information about the buffer
|
|
||||||
CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
|
|
||||||
csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
|
|
||||||
// Make sure to reset the viewport (with MoveToBottom )to where it was
|
|
||||||
// before the user scrolled the console output
|
|
||||||
bool success = (_pConApi->MoveToBottom() && _pConApi->GetConsoleScreenBufferInfoEx(csbiex));
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
COORD cursor = csbiex.dwCursorPosition;
|
|
||||||
|
|
||||||
// For next/previous line, we unconditionally need to move the X position to the left edge of the viewport.
|
|
||||||
switch (dir)
|
|
||||||
{
|
|
||||||
case CursorDirection::NextLine:
|
|
||||||
case CursorDirection::PrevLine:
|
|
||||||
cursor.X = csbiex.srWindow.Left;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safely convert the UINT magnitude of the move we were given into a short (which is the size the console deals with)
|
|
||||||
SHORT delta = 0;
|
|
||||||
success = SUCCEEDED(SizeTToShort(distance, &delta));
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
// Prepare our variables for math. All operations are some variation on these two parameters
|
|
||||||
SHORT* pModify = nullptr; // The coordinate X or Y gets modified
|
|
||||||
SHORT boundary = 0; // There is a particular edge of the viewport that is our boundary condition as we approach it.
|
|
||||||
|
|
||||||
// Up and Down modify the Y coordinate. Left and Right modify the X.
|
|
||||||
switch (dir)
|
|
||||||
{
|
|
||||||
case CursorDirection::Up:
|
|
||||||
case CursorDirection::Down:
|
|
||||||
case CursorDirection::NextLine:
|
|
||||||
case CursorDirection::PrevLine:
|
|
||||||
pModify = &cursor.Y;
|
|
||||||
break;
|
|
||||||
case CursorDirection::Left:
|
|
||||||
case CursorDirection::Right:
|
|
||||||
pModify = &cursor.X;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Moving upward is bounded by top, etc.
|
|
||||||
switch (dir)
|
|
||||||
{
|
|
||||||
case CursorDirection::Up:
|
|
||||||
case CursorDirection::PrevLine:
|
|
||||||
boundary = csbiex.srWindow.Top;
|
|
||||||
break;
|
|
||||||
case CursorDirection::Down:
|
|
||||||
case CursorDirection::NextLine:
|
|
||||||
boundary = csbiex.srWindow.Bottom;
|
|
||||||
break;
|
|
||||||
case CursorDirection::Left:
|
|
||||||
boundary = csbiex.srWindow.Left;
|
|
||||||
break;
|
|
||||||
case CursorDirection::Right:
|
|
||||||
boundary = csbiex.srWindow.Right;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success && pModify)
|
|
||||||
{
|
|
||||||
// For up and left, we need to subtract the magnitude of the vector to get the new spot. Right/down = add.
|
|
||||||
// Use safe short subtraction to prevent under/overflow.
|
|
||||||
switch (dir)
|
|
||||||
{
|
|
||||||
case CursorDirection::Up:
|
|
||||||
case CursorDirection::Left:
|
|
||||||
case CursorDirection::PrevLine:
|
|
||||||
success = SUCCEEDED(ShortSub(*pModify, delta, pModify));
|
|
||||||
break;
|
|
||||||
case CursorDirection::Down:
|
|
||||||
case CursorDirection::Right:
|
|
||||||
case CursorDirection::NextLine:
|
|
||||||
success = SUCCEEDED(ShortAdd(*pModify, delta, pModify));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
// Now apply the boundary condition. Up, Left can't be smaller than their boundary. Top, Right can't be larger.
|
|
||||||
switch (dir)
|
|
||||||
{
|
|
||||||
case CursorDirection::Up:
|
|
||||||
case CursorDirection::Left:
|
|
||||||
case CursorDirection::PrevLine:
|
|
||||||
*pModify = std::max(*pModify, boundary);
|
|
||||||
break;
|
|
||||||
case CursorDirection::Down:
|
|
||||||
case CursorDirection::Right:
|
|
||||||
case CursorDirection::NextLine:
|
|
||||||
// For the bottom and right edges, the viewport value is stated to be one outside the rectangle.
|
|
||||||
*pModify = std::min(*pModify, gsl::narrow<SHORT>(boundary - 1));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
// Finally, attempt to set the adjusted cursor position back into the console.
|
|
||||||
success = _pConApi->SetConsoleCursorPosition(cursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
// - CUU - Handles cursor upward movement by given distance.
|
// - CUU - Handles cursor upward movement by given distance.
|
||||||
// CUU and CUD are handled seperately from other CUP sequences, because they are
|
// CUU and CUD are handled seperately from other CUP sequences, because they are
|
||||||
@@ -225,12 +94,7 @@ bool AdaptDispatch::_CursorMovement(const CursorDirection dir, const size_t dist
|
|||||||
// - True if handled successfully. False otherwise.
|
// - True if handled successfully. False otherwise.
|
||||||
bool AdaptDispatch::CursorUp(const size_t distance)
|
bool AdaptDispatch::CursorUp(const size_t distance)
|
||||||
{
|
{
|
||||||
SHORT sDelta = 0;
|
return _CursorMovePosition(Offset::Backward(distance), Offset::Unchanged(), true);
|
||||||
if (SUCCEEDED(SizeTToShort(distance, &sDelta)))
|
|
||||||
{
|
|
||||||
return _pConApi->MoveCursorVertically(-sDelta);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -246,12 +110,7 @@ bool AdaptDispatch::CursorUp(const size_t distance)
|
|||||||
// - True if handled successfully. False otherwise.
|
// - True if handled successfully. False otherwise.
|
||||||
bool AdaptDispatch::CursorDown(const size_t distance)
|
bool AdaptDispatch::CursorDown(const size_t distance)
|
||||||
{
|
{
|
||||||
SHORT sDelta = 0;
|
return _CursorMovePosition(Offset::Forward(distance), Offset::Unchanged(), true);
|
||||||
if (SUCCEEDED(SizeTToShort(distance, &sDelta)))
|
|
||||||
{
|
|
||||||
return _pConApi->MoveCursorVertically(sDelta);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -262,7 +121,7 @@ bool AdaptDispatch::CursorDown(const size_t distance)
|
|||||||
// - True if handled successfully. False otherwise.
|
// - True if handled successfully. False otherwise.
|
||||||
bool AdaptDispatch::CursorForward(const size_t distance)
|
bool AdaptDispatch::CursorForward(const size_t distance)
|
||||||
{
|
{
|
||||||
return _CursorMovement(CursorDirection::Right, distance);
|
return _CursorMovePosition(Offset::Unchanged(), Offset::Forward(distance), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -273,7 +132,7 @@ bool AdaptDispatch::CursorForward(const size_t distance)
|
|||||||
// - True if handled successfully. False otherwise.
|
// - True if handled successfully. False otherwise.
|
||||||
bool AdaptDispatch::CursorBackward(const size_t distance)
|
bool AdaptDispatch::CursorBackward(const size_t distance)
|
||||||
{
|
{
|
||||||
return _CursorMovement(CursorDirection::Left, distance);
|
return _CursorMovePosition(Offset::Unchanged(), Offset::Backward(distance), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -285,7 +144,7 @@ bool AdaptDispatch::CursorBackward(const size_t distance)
|
|||||||
// - True if handled successfully. False otherwise.
|
// - True if handled successfully. False otherwise.
|
||||||
bool AdaptDispatch::CursorNextLine(const size_t distance)
|
bool AdaptDispatch::CursorNextLine(const size_t distance)
|
||||||
{
|
{
|
||||||
return _CursorMovement(CursorDirection::NextLine, distance);
|
return _CursorMovePosition(Offset::Forward(distance), Offset::Absolute(1), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -297,18 +156,18 @@ bool AdaptDispatch::CursorNextLine(const size_t distance)
|
|||||||
// - True if handled successfully. False otherwise.
|
// - True if handled successfully. False otherwise.
|
||||||
bool AdaptDispatch::CursorPrevLine(const size_t distance)
|
bool AdaptDispatch::CursorPrevLine(const size_t distance)
|
||||||
{
|
{
|
||||||
return _CursorMovement(CursorDirection::PrevLine, distance);
|
return _CursorMovePosition(Offset::Backward(distance), Offset::Absolute(1), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
// - Generalizes cursor movement to a specific coordinate position
|
// - Generalizes cursor movement to a specific position, which can be absolute or relative.
|
||||||
// - If a parameter is left blank, we will maintain the existing position in that dimension.
|
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// - row - Optional row to move to
|
// - rowOffset - The row to move to
|
||||||
// - column - Optional column to move to
|
// - colOffset - The column to move to
|
||||||
|
// - clampInMargins - Should the position be clamped within the scrolling margins
|
||||||
// Return Value:
|
// Return Value:
|
||||||
// - True if handled successfully. False otherwise.
|
// - True if handled successfully. False otherwise.
|
||||||
bool AdaptDispatch::_CursorMovePosition(const std::optional<size_t> row, const std::optional<size_t> column) const
|
bool AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins) const
|
||||||
{
|
{
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
@@ -321,81 +180,67 @@ bool AdaptDispatch::_CursorMovePosition(const std::optional<size_t> row, const s
|
|||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
// handle optional parameters. If not specified, keep same cursor position from what we just loaded.
|
// Calculate the viewport boundaries as inclusive values.
|
||||||
size_t rowActual = 0;
|
// srWindow is exclusive so we need to subtract 1 from the bottom.
|
||||||
size_t columnActual = 0;
|
const int viewportTop = csbiex.srWindow.Top;
|
||||||
|
const int viewportBottom = csbiex.srWindow.Bottom - 1;
|
||||||
|
|
||||||
if (row)
|
// Calculate the absolute margins of the scrolling area.
|
||||||
{
|
const int topMargin = viewportTop + _scrollMargins.Top;
|
||||||
if (row.value() != 0)
|
const int bottomMargin = viewportTop + _scrollMargins.Bottom;
|
||||||
{
|
const bool marginsSet = topMargin < bottomMargin;
|
||||||
rowActual = row.value() - 1; // In VT, the origin is 1,1. For our array, it's 0,0. So subtract 1.
|
|
||||||
|
|
||||||
// If the origin mode is relative, and the scrolling region is set (the bottom is non-zero),
|
// For relative movement, the given offsets will be relative to
|
||||||
// line numbers start at the top margin of the scrolling region, and cannot move below the bottom.
|
// the current cursor position.
|
||||||
if (_isOriginModeRelative && _scrollMargins.Bottom != 0)
|
int row = csbiex.dwCursorPosition.Y;
|
||||||
{
|
int col = csbiex.dwCursorPosition.X;
|
||||||
rowActual += _scrollMargins.Top;
|
|
||||||
rowActual = std::min<decltype(rowActual)>(rowActual, _scrollMargins.Bottom);
|
// But if the row is absolute, it will be relative to the top of the
|
||||||
}
|
// viewport, or the top margin, depending on the origin mode.
|
||||||
}
|
if (rowOffset.IsAbsolute)
|
||||||
else
|
|
||||||
{
|
|
||||||
success = false; // The parser should never return 0 (0 maps to 1), so this is a failure condition.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// remember, in VT speak, this is relative to the viewport. not absolute.
|
row = _isOriginModeRelative ? topMargin : viewportTop;
|
||||||
SHORT diff = 0;
|
|
||||||
success = SUCCEEDED(ShortSub(csbiex.dwCursorPosition.Y, csbiex.srWindow.Top, &diff)) && SUCCEEDED(ShortToSizeT(diff, &rowActual));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success)
|
// And if the column is absolute, it'll be relative to column 0.
|
||||||
|
// Horizontal positions are not affected by the viewport.
|
||||||
|
if (colOffset.IsAbsolute)
|
||||||
{
|
{
|
||||||
if (column)
|
col = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust the base position by the given offsets and clamp the results.
|
||||||
|
// The row is constrained within the viewport's vertical boundaries,
|
||||||
|
// while the column is constrained by the buffer width.
|
||||||
|
row = std::clamp(row + rowOffset.Value, viewportTop, viewportBottom);
|
||||||
|
col = std::clamp(col + colOffset.Value, 0, csbiex.dwSize.X - 1);
|
||||||
|
|
||||||
|
// If the operation needs to be clamped inside the margins, or the origin
|
||||||
|
// mode is relative (which always requires margin clamping), then the row
|
||||||
|
// may need to be adjusted further.
|
||||||
|
if (marginsSet && (clampInMargins || _isOriginModeRelative))
|
||||||
|
{
|
||||||
|
// See microsoft/terminal#2929 - If the cursor is _below_ the top
|
||||||
|
// margin, it should stay below the top margin. If it's _above_ the
|
||||||
|
// bottom, it should stay above the bottom. Cursor movements that stay
|
||||||
|
// outside the margins shouldn't necessarily be affected. For example,
|
||||||
|
// moving up while below the bottom margin shouldn't just jump straight
|
||||||
|
// to the bottom margin. See
|
||||||
|
// ScreenBufferTests::CursorUpDownOutsideMargins for a test of that
|
||||||
|
// behavior.
|
||||||
|
if (csbiex.dwCursorPosition.Y >= topMargin)
|
||||||
{
|
{
|
||||||
if (column.value() != 0)
|
row = std::max(row, topMargin);
|
||||||
{
|
|
||||||
columnActual = column.value() - 1; // In VT, the origin is 1,1. For our array, it's 0,0. So subtract 1.
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
success = false; // The parser should never return 0 (0 maps to 1), so this is a failure condition.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
if (csbiex.dwCursorPosition.Y <= bottomMargin)
|
||||||
{
|
{
|
||||||
// remember, in VT speak, this is relative to the viewport. not absolute.
|
row = std::min(row, bottomMargin);
|
||||||
SHORT diff = 0;
|
|
||||||
success = SUCCEEDED(ShortSub(csbiex.dwCursorPosition.X, csbiex.srWindow.Left, &diff)) && SUCCEEDED(ShortToSizeT(diff, &columnActual));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success)
|
// Finally, attempt to set the adjusted cursor position back into the console.
|
||||||
{
|
const COORD newPos = { gsl::narrow_cast<SHORT>(col), gsl::narrow_cast<SHORT>(row) };
|
||||||
COORD cursor = csbiex.dwCursorPosition;
|
success = _pConApi->SetConsoleCursorPosition(newPos);
|
||||||
|
|
||||||
// Safely convert the size_t positions we were given into shorts (which is the size the console deals with)
|
|
||||||
success = SUCCEEDED(SizeTToShort(rowActual, &cursor.Y)) && SUCCEEDED(SizeTToShort(columnActual, &cursor.X));
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
// Set the line and column values as offsets from the viewport edge. Use safe math to prevent overflow.
|
|
||||||
success = SUCCEEDED(ShortAdd(cursor.Y, csbiex.srWindow.Top, &cursor.Y)) &&
|
|
||||||
SUCCEEDED(ShortAdd(cursor.X, csbiex.srWindow.Left, &cursor.X));
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
// Apply boundary tests to ensure the cursor isn't outside the viewport rectangle.
|
|
||||||
cursor.Y = std::clamp(cursor.Y, csbiex.srWindow.Top, gsl::narrow<SHORT>(csbiex.srWindow.Bottom - 1));
|
|
||||||
cursor.X = std::clamp(cursor.X, csbiex.srWindow.Left, gsl::narrow<SHORT>(csbiex.srWindow.Right - 1));
|
|
||||||
|
|
||||||
// Finally, attempt to set the adjusted cursor position back into the console.
|
|
||||||
success = _pConApi->SetConsoleCursorPosition(cursor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
@@ -409,7 +254,7 @@ bool AdaptDispatch::_CursorMovePosition(const std::optional<size_t> row, const s
|
|||||||
// - True if handled successfully. False otherwise.
|
// - True if handled successfully. False otherwise.
|
||||||
bool AdaptDispatch::CursorHorizontalPositionAbsolute(const size_t column)
|
bool AdaptDispatch::CursorHorizontalPositionAbsolute(const size_t column)
|
||||||
{
|
{
|
||||||
return _CursorMovePosition(std::nullopt, column);
|
return _CursorMovePosition(Offset::Unchanged(), Offset::Absolute(column), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -420,7 +265,7 @@ bool AdaptDispatch::CursorHorizontalPositionAbsolute(const size_t column)
|
|||||||
// - True if handled successfully. False otherwise.
|
// - True if handled successfully. False otherwise.
|
||||||
bool AdaptDispatch::VerticalLinePositionAbsolute(const size_t line)
|
bool AdaptDispatch::VerticalLinePositionAbsolute(const size_t line)
|
||||||
{
|
{
|
||||||
return _CursorMovePosition(line, std::nullopt);
|
return _CursorMovePosition(Offset::Absolute(line), Offset::Unchanged(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -432,7 +277,7 @@ bool AdaptDispatch::VerticalLinePositionAbsolute(const size_t line)
|
|||||||
// - True if handled successfully. False otherwise.
|
// - True if handled successfully. False otherwise.
|
||||||
bool AdaptDispatch::CursorPosition(const size_t line, const size_t column)
|
bool AdaptDispatch::CursorPosition(const size_t line, const size_t column)
|
||||||
{
|
{
|
||||||
return _CursorMovePosition(line, column);
|
return _CursorMovePosition(Offset::Absolute(line), Offset::Absolute(column), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@@ -458,15 +303,14 @@ bool AdaptDispatch::CursorSaveState()
|
|||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
// The cursor is given to us by the API as relative to the whole buffer.
|
// The cursor is given to us by the API as relative to the whole buffer.
|
||||||
// But in VT speak, the cursor should be relative to the current viewport. Adjust.
|
// But in VT speak, the cursor row should be relative to the current viewport top.
|
||||||
COORD const coordCursor = csbiex.dwCursorPosition;
|
COORD coordCursor = csbiex.dwCursorPosition;
|
||||||
|
coordCursor.Y -= csbiex.srWindow.Top;
|
||||||
SMALL_RECT const srViewport = csbiex.srWindow;
|
|
||||||
|
|
||||||
// VT is also 1 based, not 0 based, so correct by 1.
|
// VT is also 1 based, not 0 based, so correct by 1.
|
||||||
auto& savedCursorState = _savedCursorState.at(_usingAltBuffer);
|
auto& savedCursorState = _savedCursorState.at(_usingAltBuffer);
|
||||||
savedCursorState.Column = coordCursor.X - srViewport.Left + 1;
|
savedCursorState.Column = coordCursor.X + 1;
|
||||||
savedCursorState.Row = coordCursor.Y - srViewport.Top + 1;
|
savedCursorState.Row = coordCursor.Y + 1;
|
||||||
savedCursorState.IsOriginModeRelative = _isOriginModeRelative;
|
savedCursorState.IsOriginModeRelative = _isOriginModeRelative;
|
||||||
savedCursorState.Attributes = attributes;
|
savedCursorState.Attributes = attributes;
|
||||||
savedCursorState.TermOutput = _termOutput;
|
savedCursorState.TermOutput = _termOutput;
|
||||||
@@ -488,7 +332,7 @@ bool AdaptDispatch::CursorRestoreState()
|
|||||||
auto& savedCursorState = _savedCursorState.at(_usingAltBuffer);
|
auto& savedCursorState = _savedCursorState.at(_usingAltBuffer);
|
||||||
|
|
||||||
auto row = savedCursorState.Row;
|
auto row = savedCursorState.Row;
|
||||||
auto col = savedCursorState.Column;
|
const auto col = savedCursorState.Column;
|
||||||
|
|
||||||
// If the origin mode is relative, and the scrolling region is set (the bottom is non-zero),
|
// If the origin mode is relative, and the scrolling region is set (the bottom is non-zero),
|
||||||
// we need to make sure the restored position is clamped within the margins.
|
// we need to make sure the restored position is clamped within the margins.
|
||||||
@@ -500,7 +344,7 @@ bool AdaptDispatch::CursorRestoreState()
|
|||||||
|
|
||||||
// The saved coordinates are always absolute, so we need reset the origin mode temporarily.
|
// The saved coordinates are always absolute, so we need reset the origin mode temporarily.
|
||||||
_isOriginModeRelative = false;
|
_isOriginModeRelative = false;
|
||||||
bool success = _CursorMovePosition(row, col);
|
bool success = CursorPosition(row, col);
|
||||||
|
|
||||||
// Once the cursor position is restored, we can then restore the actual origin mode.
|
// Once the cursor position is restored, we can then restore the actual origin mode.
|
||||||
_isOriginModeRelative = savedCursorState.IsOriginModeRelative;
|
_isOriginModeRelative = savedCursorState.IsOriginModeRelative;
|
||||||
@@ -851,8 +695,7 @@ bool AdaptDispatch::_CursorPositionReport() const
|
|||||||
// First pull the cursor position relative to the entire buffer out of the console.
|
// First pull the cursor position relative to the entire buffer out of the console.
|
||||||
COORD coordCursorPos = csbiex.dwCursorPosition;
|
COORD coordCursorPos = csbiex.dwCursorPosition;
|
||||||
|
|
||||||
// Now adjust it for its position in respect to the current viewport.
|
// Now adjust it for its position in respect to the current viewport top.
|
||||||
coordCursorPos.X -= csbiex.srWindow.Left;
|
|
||||||
coordCursorPos.Y -= csbiex.srWindow.Top;
|
coordCursorPos.Y -= csbiex.srWindow.Top;
|
||||||
|
|
||||||
// NOTE: 1,1 is the top-left corner of the viewport in VT-speak, so add 1.
|
// NOTE: 1,1 is the top-left corner of the viewport in VT-speak, so add 1.
|
||||||
|
|||||||
@@ -100,15 +100,6 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
const std::basic_string_view<size_t> parameters) override; // DTTERM_WindowManipulation
|
const std::basic_string_view<size_t> parameters) override; // DTTERM_WindowManipulation
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class CursorDirection
|
|
||||||
{
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
NextLine,
|
|
||||||
PrevLine
|
|
||||||
};
|
|
||||||
enum class ScrollDirection
|
enum class ScrollDirection
|
||||||
{
|
{
|
||||||
Up,
|
Up,
|
||||||
@@ -122,9 +113,18 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
TextAttribute Attributes = {};
|
TextAttribute Attributes = {};
|
||||||
TerminalOutput TermOutput = {};
|
TerminalOutput TermOutput = {};
|
||||||
};
|
};
|
||||||
|
struct Offset
|
||||||
|
{
|
||||||
|
int Value;
|
||||||
|
bool IsAbsolute;
|
||||||
|
// VT origin is at 1,1 so we need to subtract 1 from absolute positions.
|
||||||
|
static constexpr Offset Absolute(const size_t value) { return { gsl::narrow_cast<int>(value) - 1, true }; };
|
||||||
|
static constexpr Offset Forward(const size_t value) { return { gsl::narrow_cast<int>(value), false }; };
|
||||||
|
static constexpr Offset Backward(const size_t value) { return { -gsl::narrow_cast<int>(value), false }; };
|
||||||
|
static constexpr Offset Unchanged() { return Forward(0); };
|
||||||
|
};
|
||||||
|
|
||||||
bool _CursorMovement(const CursorDirection dir, const size_t distance) const;
|
bool _CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins) const;
|
||||||
bool _CursorMovePosition(const std::optional<size_t> row, const std::optional<size_t> column) const;
|
|
||||||
bool _EraseSingleLineHelper(const CONSOLE_SCREEN_BUFFER_INFOEX& csbiex,
|
bool _EraseSingleLineHelper(const CONSOLE_SCREEN_BUFFER_INFOEX& csbiex,
|
||||||
const DispatchTypes::EraseType eraseType,
|
const DispatchTypes::EraseType eraseType,
|
||||||
const size_t lineId) const;
|
const size_t lineId) const;
|
||||||
|
|||||||
@@ -91,8 +91,6 @@ namespace Microsoft::Console::VirtualTerminal
|
|||||||
virtual bool PrivateSuppressResizeRepaint() = 0;
|
virtual bool PrivateSuppressResizeRepaint() = 0;
|
||||||
virtual bool IsConsolePty(bool& isPty) const = 0;
|
virtual bool IsConsolePty(bool& isPty) const = 0;
|
||||||
|
|
||||||
virtual bool MoveCursorVertically(const ptrdiff_t lines) = 0;
|
|
||||||
|
|
||||||
virtual bool DeleteLines(const size_t count) = 0;
|
virtual bool DeleteLines(const size_t count) = 0;
|
||||||
virtual bool InsertLines(const size_t count) = 0;
|
virtual bool InsertLines(const size_t count) = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -376,19 +376,6 @@ public:
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MoveCursorVertically(const ptrdiff_t lines) override
|
|
||||||
{
|
|
||||||
Log::Comment(L"MoveCursorVertically MOCK called...");
|
|
||||||
short l;
|
|
||||||
VERIFY_SUCCEEDED(PtrdiffTToShort(lines, &l));
|
|
||||||
if (_moveCursorVerticallyResult)
|
|
||||||
{
|
|
||||||
VERIFY_ARE_EQUAL(_expectedLines, l);
|
|
||||||
_cursorPos = { _cursorPos.X, _cursorPos.Y + l };
|
|
||||||
}
|
|
||||||
return !!_moveCursorVerticallyResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SetConsoleTitleW(const std::wstring_view title)
|
bool SetConsoleTitleW(const std::wstring_view title)
|
||||||
{
|
{
|
||||||
Log::Comment(L"SetConsoleTitleW MOCK called...");
|
Log::Comment(L"SetConsoleTitleW MOCK called...");
|
||||||
@@ -745,8 +732,6 @@ public:
|
|||||||
// Attribute default is gray on black.
|
// Attribute default is gray on black.
|
||||||
_attribute = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
|
_attribute = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
|
||||||
_expectedAttribute = _attribute;
|
_expectedAttribute = _attribute;
|
||||||
|
|
||||||
_expectedLines = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrepCursor(CursorX xact, CursorY yact)
|
void PrepCursor(CursorX xact, CursorY yact)
|
||||||
@@ -756,16 +741,16 @@ public:
|
|||||||
switch (xact)
|
switch (xact)
|
||||||
{
|
{
|
||||||
case CursorX::LEFT:
|
case CursorX::LEFT:
|
||||||
Log::Comment(L"Cursor set to left edge of viewport.");
|
Log::Comment(L"Cursor set to left edge of buffer.");
|
||||||
_cursorPos.X = _viewport.Left;
|
_cursorPos.X = 0;
|
||||||
break;
|
break;
|
||||||
case CursorX::RIGHT:
|
case CursorX::RIGHT:
|
||||||
Log::Comment(L"Cursor set to right edge of viewport.");
|
Log::Comment(L"Cursor set to right edge of buffer.");
|
||||||
_cursorPos.X = _viewport.Right - 1;
|
_cursorPos.X = _bufferSize.X - 1;
|
||||||
break;
|
break;
|
||||||
case CursorX::XCENTER:
|
case CursorX::XCENTER:
|
||||||
Log::Comment(L"Cursor set to centered X of viewport.");
|
Log::Comment(L"Cursor set to centered X of buffer.");
|
||||||
_cursorPos.X = _viewport.Left + ((_viewport.Right - _viewport.Left) / 2);
|
_cursorPos.X = _bufferSize.X / 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -862,7 +847,6 @@ public:
|
|||||||
bool _expectedMeta = false;
|
bool _expectedMeta = false;
|
||||||
unsigned int _expectedOutputCP = 0;
|
unsigned int _expectedOutputCP = 0;
|
||||||
bool _isPty = false;
|
bool _isPty = false;
|
||||||
short _expectedLines = 0;
|
|
||||||
bool _privateBoldTextResult = false;
|
bool _privateBoldTextResult = false;
|
||||||
bool _expectedIsBold = false;
|
bool _expectedIsBold = false;
|
||||||
bool _isBold = false;
|
bool _isBold = false;
|
||||||
@@ -921,7 +905,6 @@ public:
|
|||||||
COLORREF _expectedCursorColor = 0;
|
COLORREF _expectedCursorColor = 0;
|
||||||
bool _getConsoleOutputCPResult = false;
|
bool _getConsoleOutputCPResult = false;
|
||||||
bool _isConsolePtyResult = false;
|
bool _isConsolePtyResult = false;
|
||||||
bool _moveCursorVerticallyResult = false;
|
|
||||||
bool _privateSetDefaultAttributesResult = false;
|
bool _privateSetDefaultAttributesResult = false;
|
||||||
bool _moveToBottomResult = false;
|
bool _moveToBottomResult = false;
|
||||||
|
|
||||||
@@ -1041,24 +1024,6 @@ public:
|
|||||||
Log::Comment(L"Test 1: Cursor doesn't move when placed in corner of viewport.");
|
Log::Comment(L"Test 1: Cursor doesn't move when placed in corner of viewport.");
|
||||||
_testGetSet->PrepData(direction);
|
_testGetSet->PrepData(direction);
|
||||||
|
|
||||||
switch (direction)
|
|
||||||
{
|
|
||||||
case CursorDirection::UP:
|
|
||||||
Log::Comment(L"Testing up direction.");
|
|
||||||
_testGetSet->_expectedLines = -1;
|
|
||||||
_testGetSet->_moveCursorVerticallyResult = true;
|
|
||||||
break;
|
|
||||||
case CursorDirection::DOWN:
|
|
||||||
Log::Comment(L"Testing down direction.");
|
|
||||||
_testGetSet->_expectedLines = 1;
|
|
||||||
_testGetSet->_moveCursorVerticallyResult = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
_testGetSet->_expectedLines = 0;
|
|
||||||
_testGetSet->_moveCursorVerticallyResult = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
|
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
|
||||||
|
|
||||||
Log::Comment(L"Test 1b: Cursor moves to left of line with next/prev line command when cursor can't move higher/lower.");
|
Log::Comment(L"Test 1b: Cursor moves to left of line with next/prev line command when cursor can't move higher/lower.");
|
||||||
@@ -1079,7 +1044,7 @@ public:
|
|||||||
|
|
||||||
if (fDoTest1b)
|
if (fDoTest1b)
|
||||||
{
|
{
|
||||||
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
|
_testGetSet->_expectedCursorPos.X = 0;
|
||||||
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
|
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(1));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1095,13 +1060,9 @@ public:
|
|||||||
{
|
{
|
||||||
case CursorDirection::UP:
|
case CursorDirection::UP:
|
||||||
_testGetSet->_expectedCursorPos.Y--;
|
_testGetSet->_expectedCursorPos.Y--;
|
||||||
_testGetSet->_expectedLines = -1;
|
|
||||||
_testGetSet->_moveCursorVerticallyResult = true;
|
|
||||||
break;
|
break;
|
||||||
case CursorDirection::DOWN:
|
case CursorDirection::DOWN:
|
||||||
_testGetSet->_expectedCursorPos.Y++;
|
_testGetSet->_expectedCursorPos.Y++;
|
||||||
_testGetSet->_expectedLines = 1;
|
|
||||||
_testGetSet->_moveCursorVerticallyResult = true;
|
|
||||||
break;
|
break;
|
||||||
case CursorDirection::RIGHT:
|
case CursorDirection::RIGHT:
|
||||||
_testGetSet->_expectedCursorPos.X++;
|
_testGetSet->_expectedCursorPos.X++;
|
||||||
@@ -1111,11 +1072,11 @@ public:
|
|||||||
break;
|
break;
|
||||||
case CursorDirection::NEXTLINE:
|
case CursorDirection::NEXTLINE:
|
||||||
_testGetSet->_expectedCursorPos.Y++;
|
_testGetSet->_expectedCursorPos.Y++;
|
||||||
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
|
_testGetSet->_expectedCursorPos.X = 0;
|
||||||
break;
|
break;
|
||||||
case CursorDirection::PREVLINE:
|
case CursorDirection::PREVLINE:
|
||||||
_testGetSet->_expectedCursorPos.Y--;
|
_testGetSet->_expectedCursorPos.Y--;
|
||||||
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
|
_testGetSet->_expectedCursorPos.X = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1131,26 +1092,22 @@ public:
|
|||||||
{
|
{
|
||||||
case CursorDirection::UP:
|
case CursorDirection::UP:
|
||||||
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
|
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
|
||||||
_testGetSet->_expectedLines = -100;
|
|
||||||
_testGetSet->_moveCursorVerticallyResult = true;
|
|
||||||
break;
|
break;
|
||||||
case CursorDirection::DOWN:
|
case CursorDirection::DOWN:
|
||||||
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
|
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
|
||||||
_testGetSet->_expectedLines = 100;
|
|
||||||
_testGetSet->_moveCursorVerticallyResult = true;
|
|
||||||
break;
|
break;
|
||||||
case CursorDirection::RIGHT:
|
case CursorDirection::RIGHT:
|
||||||
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Right - 1;
|
_testGetSet->_expectedCursorPos.X = _testGetSet->_bufferSize.X - 1;
|
||||||
break;
|
break;
|
||||||
case CursorDirection::LEFT:
|
case CursorDirection::LEFT:
|
||||||
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
|
_testGetSet->_expectedCursorPos.X = 0;
|
||||||
break;
|
break;
|
||||||
case CursorDirection::NEXTLINE:
|
case CursorDirection::NEXTLINE:
|
||||||
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
|
_testGetSet->_expectedCursorPos.X = 0;
|
||||||
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
|
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
|
||||||
break;
|
break;
|
||||||
case CursorDirection::PREVLINE:
|
case CursorDirection::PREVLINE:
|
||||||
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
|
_testGetSet->_expectedCursorPos.X = 0;
|
||||||
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
|
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1158,64 +1115,19 @@ public:
|
|||||||
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(100));
|
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(100));
|
||||||
|
|
||||||
// error cases
|
// error cases
|
||||||
// give too large an up distance, cursor move should fail, cursor should stay the same.
|
|
||||||
Log::Comment(L"Test 4: When given invalid (massive) move distance that doesn't fit in a short, call fails and cursor doesn't move.");
|
|
||||||
_testGetSet->PrepData(CursorX::XCENTER, CursorY::YCENTER);
|
|
||||||
|
|
||||||
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(UINT_MAX));
|
|
||||||
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
|
|
||||||
|
|
||||||
// cause short underflow. cursor move should fail. cursor should stay the same.
|
|
||||||
Log::Comment(L"Test 5: When an over/underflow occurs in cursor math, call fails and cursor doesn't move.");
|
|
||||||
_testGetSet->PrepData(direction);
|
|
||||||
|
|
||||||
switch (direction)
|
|
||||||
{
|
|
||||||
case CursorDirection::UP:
|
|
||||||
case CursorDirection::PREVLINE:
|
|
||||||
_testGetSet->_cursorPos.Y = -10;
|
|
||||||
break;
|
|
||||||
case CursorDirection::DOWN:
|
|
||||||
case CursorDirection::NEXTLINE:
|
|
||||||
_testGetSet->_cursorPos.Y = 10;
|
|
||||||
break;
|
|
||||||
case CursorDirection::RIGHT:
|
|
||||||
_testGetSet->_cursorPos.X = 10;
|
|
||||||
break;
|
|
||||||
case CursorDirection::LEFT:
|
|
||||||
_testGetSet->_cursorPos.X = -10;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_testGetSet->_expectedCursorPos = _testGetSet->_cursorPos;
|
|
||||||
|
|
||||||
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(SHRT_MAX + 1));
|
|
||||||
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
|
|
||||||
|
|
||||||
// SetConsoleCursorPosition throws failure. Parameters are otherwise normal.
|
// SetConsoleCursorPosition throws failure. Parameters are otherwise normal.
|
||||||
Log::Comment(L"Test 6: When SetConsoleCursorPosition throws a failure, call fails and cursor doesn't move.");
|
Log::Comment(L"Test 4: When SetConsoleCursorPosition throws a failure, call fails and cursor doesn't move.");
|
||||||
_testGetSet->PrepData(direction);
|
_testGetSet->PrepData(direction);
|
||||||
_testGetSet->_setConsoleCursorPositionResult = FALSE;
|
_testGetSet->_setConsoleCursorPositionResult = FALSE;
|
||||||
_testGetSet->_moveCursorVerticallyResult = false;
|
|
||||||
|
|
||||||
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(0));
|
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(0));
|
||||||
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
|
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
|
||||||
|
|
||||||
// GetConsoleScreenBufferInfo throws failure. Parameters are otherwise normal.
|
// GetConsoleScreenBufferInfo throws failure. Parameters are otherwise normal.
|
||||||
Log::Comment(L"Test 7: When GetConsoleScreenBufferInfo throws a failure, call fails and cursor doesn't move.");
|
Log::Comment(L"Test 5: When GetConsoleScreenBufferInfo throws a failure, call fails and cursor doesn't move.");
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
||||||
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
|
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
|
||||||
_testGetSet->_moveCursorVerticallyResult = true;
|
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(0));
|
||||||
Log::Comment(NoThrowString().Format(
|
|
||||||
L"Cursor Up and Down don't need GetConsoleScreenBufferInfoEx, so they will succeed"));
|
|
||||||
if (direction == CursorDirection::UP || direction == CursorDirection::DOWN)
|
|
||||||
{
|
|
||||||
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(0));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(0));
|
|
||||||
}
|
|
||||||
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
|
VERIFY_ARE_EQUAL(_testGetSet->_expectedCursorPos, _testGetSet->_cursorPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1229,7 +1141,8 @@ public:
|
|||||||
short sCol = (_testGetSet->_viewport.Right - _testGetSet->_viewport.Left) / 2;
|
short sCol = (_testGetSet->_viewport.Right - _testGetSet->_viewport.Left) / 2;
|
||||||
short sRow = (_testGetSet->_viewport.Bottom - _testGetSet->_viewport.Top) / 2;
|
short sRow = (_testGetSet->_viewport.Bottom - _testGetSet->_viewport.Top) / 2;
|
||||||
|
|
||||||
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left + (sCol - 1);
|
// The X coordinate is unaffected by the viewport.
|
||||||
|
_testGetSet->_expectedCursorPos.X = sCol - 1;
|
||||||
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top + (sRow - 1);
|
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top + (sRow - 1);
|
||||||
|
|
||||||
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(sRow, sCol));
|
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(sRow, sCol));
|
||||||
@@ -1237,7 +1150,8 @@ public:
|
|||||||
Log::Comment(L"Test 2: Move to 0, 0 (which is 1,1 in VT speak)");
|
Log::Comment(L"Test 2: Move to 0, 0 (which is 1,1 in VT speak)");
|
||||||
_testGetSet->PrepData(CursorX::RIGHT, CursorY::BOTTOM);
|
_testGetSet->PrepData(CursorX::RIGHT, CursorY::BOTTOM);
|
||||||
|
|
||||||
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Left;
|
// The X coordinate is unaffected by the viewport.
|
||||||
|
_testGetSet->_expectedCursorPos.X = 0;
|
||||||
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
|
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Top;
|
||||||
|
|
||||||
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(1, 1));
|
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(1, 1));
|
||||||
@@ -1245,45 +1159,27 @@ public:
|
|||||||
Log::Comment(L"Test 3: Move beyond rectangle (down/right too far). Should be bounded back in.");
|
Log::Comment(L"Test 3: Move beyond rectangle (down/right too far). Should be bounded back in.");
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
||||||
|
|
||||||
sCol = (_testGetSet->_viewport.Right - _testGetSet->_viewport.Left) * 2;
|
sCol = (_testGetSet->_bufferSize.X) * 2;
|
||||||
sRow = (_testGetSet->_viewport.Bottom - _testGetSet->_viewport.Top) * 2;
|
sRow = (_testGetSet->_viewport.Bottom - _testGetSet->_viewport.Top) * 2;
|
||||||
|
|
||||||
_testGetSet->_expectedCursorPos.X = _testGetSet->_viewport.Right - 1;
|
_testGetSet->_expectedCursorPos.X = _testGetSet->_bufferSize.X - 1;
|
||||||
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
|
_testGetSet->_expectedCursorPos.Y = _testGetSet->_viewport.Bottom - 1;
|
||||||
|
|
||||||
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(sRow, sCol));
|
VERIFY_IS_TRUE(_pDispatch.get()->CursorPosition(sRow, sCol));
|
||||||
|
|
||||||
Log::Comment(L"Test 4: Values too large for short. Cursor shouldn't move. Return false.");
|
Log::Comment(L"Test 4: GetConsoleInfo API returns false. No move, return false.");
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
|
||||||
|
|
||||||
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(UINT_MAX, UINT_MAX));
|
|
||||||
|
|
||||||
Log::Comment(L"Test 5: Overflow during addition. Cursor shouldn't move. Return false.");
|
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
|
||||||
|
|
||||||
_testGetSet->_viewport.Left = SHRT_MAX;
|
|
||||||
_testGetSet->_viewport.Top = SHRT_MAX;
|
|
||||||
|
|
||||||
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(5, 5));
|
|
||||||
|
|
||||||
Log::Comment(L"Test 6: GetConsoleInfo API returns false. No move, return false.");
|
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
||||||
|
|
||||||
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
|
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
|
||||||
|
|
||||||
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(1, 1));
|
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(1, 1));
|
||||||
|
|
||||||
Log::Comment(L"Test 7: SetCursor API returns false. No move, return false.");
|
Log::Comment(L"Test 5: SetCursor API returns false. No move, return false.");
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
||||||
|
|
||||||
_testGetSet->_setConsoleCursorPositionResult = FALSE;
|
_testGetSet->_setConsoleCursorPositionResult = FALSE;
|
||||||
|
|
||||||
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(1, 1));
|
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(1, 1));
|
||||||
|
|
||||||
Log::Comment(L"Test 8: Move to 0,0. Cursor shouldn't move. Return false. 1,1 is the top left corner in VT100 speak. 0,0 isn't a position. The parser will give 1 for a 0 input.");
|
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
|
||||||
|
|
||||||
VERIFY_IS_FALSE(_pDispatch.get()->CursorPosition(0, 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_METHOD(CursorSingleDimensionMoveTest)
|
TEST_METHOD(CursorSingleDimensionMoveTest)
|
||||||
@@ -1297,8 +1193,8 @@ public:
|
|||||||
//// Used to switch between the various function options.
|
//// Used to switch between the various function options.
|
||||||
typedef bool (AdaptDispatch::*CursorMoveFunc)(size_t);
|
typedef bool (AdaptDispatch::*CursorMoveFunc)(size_t);
|
||||||
CursorMoveFunc moveFunc = nullptr;
|
CursorMoveFunc moveFunc = nullptr;
|
||||||
SHORT* psViewportEnd = nullptr;
|
SHORT sRangeEnd = 0;
|
||||||
SHORT* psViewportStart = nullptr;
|
SHORT sRangeStart = 0;
|
||||||
SHORT* psCursorExpected = nullptr;
|
SHORT* psCursorExpected = nullptr;
|
||||||
|
|
||||||
// Modify variables based on directionality of this test
|
// Modify variables based on directionality of this test
|
||||||
@@ -1306,26 +1202,27 @@ public:
|
|||||||
size_t dir;
|
size_t dir;
|
||||||
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiDirection", dir));
|
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiDirection", dir));
|
||||||
direction = (AbsolutePosition)dir;
|
direction = (AbsolutePosition)dir;
|
||||||
|
_testGetSet->PrepData();
|
||||||
|
|
||||||
switch (direction)
|
switch (direction)
|
||||||
{
|
{
|
||||||
case AbsolutePosition::CursorHorizontal:
|
case AbsolutePosition::CursorHorizontal:
|
||||||
Log::Comment(L"Testing cursor horizontal movement.");
|
Log::Comment(L"Testing cursor horizontal movement.");
|
||||||
psViewportEnd = &_testGetSet->_viewport.Right;
|
sRangeEnd = _testGetSet->_bufferSize.X;
|
||||||
psViewportStart = &_testGetSet->_viewport.Left;
|
sRangeStart = 0;
|
||||||
psCursorExpected = &_testGetSet->_expectedCursorPos.X;
|
psCursorExpected = &_testGetSet->_expectedCursorPos.X;
|
||||||
moveFunc = &AdaptDispatch::CursorHorizontalPositionAbsolute;
|
moveFunc = &AdaptDispatch::CursorHorizontalPositionAbsolute;
|
||||||
break;
|
break;
|
||||||
case AbsolutePosition::VerticalLine:
|
case AbsolutePosition::VerticalLine:
|
||||||
Log::Comment(L"Testing vertical line movement.");
|
Log::Comment(L"Testing vertical line movement.");
|
||||||
psViewportEnd = &_testGetSet->_viewport.Bottom;
|
sRangeEnd = _testGetSet->_viewport.Bottom;
|
||||||
psViewportStart = &_testGetSet->_viewport.Top;
|
sRangeStart = _testGetSet->_viewport.Top;
|
||||||
psCursorExpected = &_testGetSet->_expectedCursorPos.Y;
|
psCursorExpected = &_testGetSet->_expectedCursorPos.Y;
|
||||||
moveFunc = &AdaptDispatch::VerticalLinePositionAbsolute;
|
moveFunc = &AdaptDispatch::VerticalLinePositionAbsolute;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (moveFunc == nullptr || psViewportEnd == nullptr || psViewportStart == nullptr || psCursorExpected == nullptr)
|
if (moveFunc == nullptr || psCursorExpected == nullptr)
|
||||||
{
|
{
|
||||||
VERIFY_FAIL();
|
VERIFY_FAIL();
|
||||||
return;
|
return;
|
||||||
@@ -1334,16 +1231,16 @@ public:
|
|||||||
Log::Comment(L"Test 1: Place cursor within the viewport. Start from top left, move to middle.");
|
Log::Comment(L"Test 1: Place cursor within the viewport. Start from top left, move to middle.");
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
||||||
|
|
||||||
short sVal = (*psViewportEnd - *psViewportStart) / 2;
|
short sVal = (sRangeEnd - sRangeStart) / 2;
|
||||||
|
|
||||||
*psCursorExpected = *psViewportStart + (sVal - 1);
|
*psCursorExpected = sRangeStart + (sVal - 1);
|
||||||
|
|
||||||
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
|
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
|
||||||
|
|
||||||
Log::Comment(L"Test 2: Move to 0 (which is 1 in VT speak)");
|
Log::Comment(L"Test 2: Move to 0 (which is 1 in VT speak)");
|
||||||
_testGetSet->PrepData(CursorX::RIGHT, CursorY::BOTTOM);
|
_testGetSet->PrepData(CursorX::RIGHT, CursorY::BOTTOM);
|
||||||
|
|
||||||
*psCursorExpected = *psViewportStart;
|
*psCursorExpected = sRangeStart;
|
||||||
sVal = 1;
|
sVal = 1;
|
||||||
|
|
||||||
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
|
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
|
||||||
@@ -1351,29 +1248,13 @@ public:
|
|||||||
Log::Comment(L"Test 3: Move beyond rectangle (down/right too far). Should be bounded back in.");
|
Log::Comment(L"Test 3: Move beyond rectangle (down/right too far). Should be bounded back in.");
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
||||||
|
|
||||||
sVal = (*psViewportEnd - *psViewportStart) * 2;
|
sVal = (sRangeEnd - sRangeStart) * 2;
|
||||||
|
|
||||||
*psCursorExpected = *psViewportEnd - 1;
|
*psCursorExpected = sRangeEnd - 1;
|
||||||
|
|
||||||
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
|
VERIFY_IS_TRUE((_pDispatch.get()->*(moveFunc))(sVal));
|
||||||
|
|
||||||
Log::Comment(L"Test 4: Values too large for short. Cursor shouldn't move. Return false.");
|
Log::Comment(L"Test 4: GetConsoleInfo API returns false. No move, return false.");
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
|
||||||
|
|
||||||
sVal = SHORT_MAX;
|
|
||||||
|
|
||||||
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
|
|
||||||
|
|
||||||
Log::Comment(L"Test 5: Overflow during addition. Cursor shouldn't move. Return false.");
|
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
|
||||||
|
|
||||||
_testGetSet->_viewport.Left = SHRT_MAX;
|
|
||||||
|
|
||||||
sVal = 5;
|
|
||||||
|
|
||||||
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
|
|
||||||
|
|
||||||
Log::Comment(L"Test 6: GetConsoleInfo API returns false. No move, return false.");
|
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
||||||
|
|
||||||
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
|
_testGetSet->_getConsoleScreenBufferInfoExResult = FALSE;
|
||||||
@@ -1382,7 +1263,7 @@ public:
|
|||||||
|
|
||||||
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
|
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
|
||||||
|
|
||||||
Log::Comment(L"Test 7: SetCursor API returns false. No move, return false.");
|
Log::Comment(L"Test 5: SetCursor API returns false. No move, return false.");
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
||||||
|
|
||||||
_testGetSet->_setConsoleCursorPositionResult = FALSE;
|
_testGetSet->_setConsoleCursorPositionResult = FALSE;
|
||||||
@@ -1390,13 +1271,6 @@ public:
|
|||||||
sVal = 1;
|
sVal = 1;
|
||||||
|
|
||||||
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
|
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
|
||||||
|
|
||||||
Log::Comment(L"Test 8: Move to 0. Cursor shouldn't move. Return false. 1 is the left edge in VT100 speak. 0 isn't a position. The parser will give 1 for a 0 input.");
|
|
||||||
_testGetSet->PrepData(CursorX::LEFT, CursorY::TOP);
|
|
||||||
|
|
||||||
sVal = 0;
|
|
||||||
|
|
||||||
VERIFY_IS_FALSE((_pDispatch.get()->*(moveFunc))(sVal));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_METHOD(CursorSaveRestoreTest)
|
TEST_METHOD(CursorSaveRestoreTest)
|
||||||
@@ -1921,8 +1795,7 @@ public:
|
|||||||
// start with the cursor position in the buffer.
|
// start with the cursor position in the buffer.
|
||||||
COORD coordCursorExpected = _testGetSet->_cursorPos;
|
COORD coordCursorExpected = _testGetSet->_cursorPos;
|
||||||
|
|
||||||
// to get to VT, we have to adjust it to its position relative to the viewport.
|
// to get to VT, we have to adjust it to its position relative to the viewport top.
|
||||||
coordCursorExpected.X -= _testGetSet->_viewport.Left;
|
|
||||||
coordCursorExpected.Y -= _testGetSet->_viewport.Top;
|
coordCursorExpected.Y -= _testGetSet->_viewport.Top;
|
||||||
|
|
||||||
// Then note that VT is 1,1 based for the top left, so add 1. (The rest of the console uses 0,0 for array index bases.)
|
// Then note that VT is 1,1 based for the top left, so add 1. (The rest of the console uses 0,0 for array index bases.)
|
||||||
@@ -2020,6 +1893,7 @@ public:
|
|||||||
Log::Comment(L"Starting test...");
|
Log::Comment(L"Starting test...");
|
||||||
|
|
||||||
SMALL_RECT srTestMargins = { 0 };
|
SMALL_RECT srTestMargins = { 0 };
|
||||||
|
_testGetSet->_bufferSize = { 100, 600 };
|
||||||
_testGetSet->_viewport.Right = 8;
|
_testGetSet->_viewport.Right = 8;
|
||||||
_testGetSet->_viewport.Bottom = 8;
|
_testGetSet->_viewport.Bottom = 8;
|
||||||
_testGetSet->_getConsoleScreenBufferInfoExResult = TRUE;
|
_testGetSet->_getConsoleScreenBufferInfoExResult = TRUE;
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ bool InputStateMachineEngine::_DoControlCharacter(const wchar_t wch, const bool
|
|||||||
{
|
{
|
||||||
// This is a C0 Control Character.
|
// This is a C0 Control Character.
|
||||||
// This should be translated as Ctrl+(wch+x40)
|
// This should be translated as Ctrl+(wch+x40)
|
||||||
const wchar_t actualChar = wch;
|
wchar_t actualChar = wch;
|
||||||
bool writeCtrl = true;
|
bool writeCtrl = true;
|
||||||
|
|
||||||
short vkey = 0;
|
short vkey = 0;
|
||||||
@@ -213,7 +213,9 @@ bool InputStateMachineEngine::_DoControlCharacter(const wchar_t wch, const bool
|
|||||||
switch (wch)
|
switch (wch)
|
||||||
{
|
{
|
||||||
case L'\b':
|
case L'\b':
|
||||||
success = _GenerateKeyFromChar(wch + 0x40, vkey, modifierState);
|
// Process Ctrl+Bksp to delete whole words
|
||||||
|
actualChar = '\x7f';
|
||||||
|
success = _GenerateKeyFromChar(actualChar, vkey, modifierState);
|
||||||
modifierState = 0;
|
modifierState = 0;
|
||||||
break;
|
break;
|
||||||
case L'\r':
|
case L'\r':
|
||||||
|
|||||||
@@ -1269,13 +1269,17 @@ void StateMachine::ProcessString(const std::wstring_view string)
|
|||||||
{
|
{
|
||||||
if (_isActionableFromGround(string.at(current))) // If the current char is the start of an escape sequence, or should be executed in ground state...
|
if (_isActionableFromGround(string.at(current))) // If the current char is the start of an escape sequence, or should be executed in ground state...
|
||||||
{
|
{
|
||||||
// Because the _run above is composed INCLUDING current, we must
|
if (!_run.empty())
|
||||||
// trim it off here since we just determined it's actionable
|
{
|
||||||
// and only pass through everything before it.
|
// Because the _run above is composed INCLUDING current, we must
|
||||||
const auto allLeadingUpTo = _run.substr(0, _run.size() - 1);
|
// trim it off here since we just determined it's actionable
|
||||||
|
// and only pass through everything before it.
|
||||||
|
const auto allLeadingUpTo = _run.substr(0, _run.size() - 1);
|
||||||
|
|
||||||
|
_engine->ActionPrintString(allLeadingUpTo); // ... print all the chars leading up to it as part of the run...
|
||||||
|
_trace.DispatchPrintRunTrace(allLeadingUpTo);
|
||||||
|
}
|
||||||
|
|
||||||
_engine->ActionPrintString(allLeadingUpTo); // ... print all the chars leading up to it as part of the run...
|
|
||||||
_trace.DispatchPrintRunTrace(allLeadingUpTo);
|
|
||||||
_processingIndividually = true; // begin processing future characters individually...
|
_processingIndividually = true; // begin processing future characters individually...
|
||||||
start = current;
|
start = current;
|
||||||
continue;
|
continue;
|
||||||
@@ -1290,9 +1294,7 @@ void StateMachine::ProcessString(const std::wstring_view string)
|
|||||||
// When we leave the loop, current has been advanced to the length of the string itself
|
// When we leave the loop, current has been advanced to the length of the string itself
|
||||||
// (or one past the array index to the final char) so this `substr` operation doesn't +1
|
// (or one past the array index to the final char) so this `substr` operation doesn't +1
|
||||||
// to include the final character (unlike the one inside the top of the loop above.)
|
// to include the final character (unlike the one inside the top of the loop above.)
|
||||||
// NOTE: std::basic_string_view will auto-trim excessively large sizes down to the valid length
|
_run = start < string.size() ? string.substr(start) : std::wstring_view{};
|
||||||
// so passing something too large in the second parameter WILL NOT FAIL.
|
|
||||||
_run = string.substr(start, current - start);
|
|
||||||
|
|
||||||
// If we're at the end of the string and have remaining un-printed characters,
|
// If we're at the end of the string and have remaining un-printed characters,
|
||||||
if (!_processingIndividually && !_run.empty())
|
if (!_processingIndividually && !_run.empty())
|
||||||
|
|||||||
@@ -349,6 +349,10 @@ void InputEngineTest::C0Test()
|
|||||||
case L'\t': // Tab
|
case L'\t': // Tab
|
||||||
writeCtrl = false;
|
writeCtrl = false;
|
||||||
break;
|
break;
|
||||||
|
case L'\b': // backspace
|
||||||
|
wch = '\x7f';
|
||||||
|
expectedWch = '\x7f';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
short keyscan = VkKeyScanW(expectedWch);
|
short keyscan = VkKeyScanW(expectedWch);
|
||||||
|
|||||||
@@ -82,7 +82,8 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken,
|
|||||||
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(signalPipeConhostSide.addressof(), signalPipeOurSide.addressof(), &sa, 0));
|
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(signalPipeConhostSide.addressof(), signalPipeOurSide.addressof(), &sa, 0));
|
||||||
RETURN_IF_WIN32_BOOL_FALSE(SetHandleInformation(signalPipeConhostSide.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
|
RETURN_IF_WIN32_BOOL_FALSE(SetHandleInformation(signalPipeConhostSide.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
|
||||||
|
|
||||||
const wchar_t* pwszFormat = L"%s --headless %s--width %hu --height %hu --signal 0x%x --server 0x%x";
|
// GH4061: Ensure that the path to executable in the format is escaped so C:\Program.exe cannot collide with C:\Program Files
|
||||||
|
const wchar_t* pwszFormat = L"\"%s\" --headless %s--width %hu --height %hu --signal 0x%x --server 0x%x";
|
||||||
// This is plenty of space to hold the formatted string
|
// This is plenty of space to hold the formatted string
|
||||||
wchar_t cmd[MAX_PATH]{};
|
wchar_t cmd[MAX_PATH]{};
|
||||||
const BOOL bInheritCursor = (dwFlags & PSEUDOCONSOLE_INHERIT_CURSOR) == PSEUDOCONSOLE_INHERIT_CURSOR;
|
const BOOL bInheritCursor = (dwFlags & PSEUDOCONSOLE_INHERIT_CURSOR) == PSEUDOCONSOLE_INHERIT_CURSOR;
|
||||||
@@ -149,7 +150,7 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken,
|
|||||||
if (hToken == INVALID_HANDLE_VALUE || hToken == nullptr)
|
if (hToken == INVALID_HANDLE_VALUE || hToken == nullptr)
|
||||||
{
|
{
|
||||||
// Call create process
|
// Call create process
|
||||||
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(nullptr,
|
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(_ConsoleHostPath(),
|
||||||
cmd,
|
cmd,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
@@ -164,7 +165,7 @@ HRESULT _CreatePseudoConsole(const HANDLE hToken,
|
|||||||
{
|
{
|
||||||
// Call create process
|
// Call create process
|
||||||
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessAsUserW(hToken,
|
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessAsUserW(hToken,
|
||||||
nullptr,
|
_ConsoleHostPath(),
|
||||||
cmd,
|
cmd,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
|
|||||||
Reference in New Issue
Block a user