Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement preventing auto-scroll on new output #6062

Merged
8 commits merged into from
Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,33 @@
"description": "When set to true, the window will scroll to the command input line when typing. When set to false, the window will not scroll when you start typing.",
"type": "boolean"
},
"snapOnOutput": {
"default": [ "noSelection", "atBottom"],
"description": "Controls the terminal's scroll response to new output. Possible values include:\n -\"always\"\n -\"atBottom\"\n -\"noSelection\"\n -\"never\"\nMultiple can be enforced in an array format.",
"oneOf": [
{
"type": "string",
"enum": [
"always",
"atBottom",
"noSelection",
"never"
]
},
{
"type": "array",
"items": {
"type": "string",
"enum": [
"always",
"atBottom",
"noSelection",
"never"
]
}
}
]
},
"source": {
"description": "Stores the name of the profile generator that originated this profile.",
"type": ["string", "null"]
Expand Down
86 changes: 86 additions & 0 deletions src/cascadia/TerminalApp/Profile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ static constexpr std::string_view TabTitleKey{ "tabTitle" };
static constexpr std::string_view SuppressApplicationTitleKey{ "suppressApplicationTitle" };
static constexpr std::string_view HistorySizeKey{ "historySize" };
static constexpr std::string_view SnapOnInputKey{ "snapOnInput" };
static constexpr std::string_view SnapOnOutputKey{ "snapOnOutput" };
static constexpr std::string_view CursorColorKey{ "cursorColor" };
static constexpr std::string_view CursorShapeKey{ "cursorShape" };
static constexpr std::string_view CursorHeightKey{ "cursorHeight" };
Expand Down Expand Up @@ -88,6 +89,12 @@ static constexpr std::wstring_view AntialiasingModeGrayscale{ L"grayscale" };
static constexpr std::wstring_view AntialiasingModeCleartype{ L"cleartype" };
static constexpr std::wstring_view AntialiasingModeAliased{ L"aliased" };

// Possible values for TextAntialiasingMode
static constexpr std::string_view SnapOnOutputNever{ "never" };
static constexpr std::string_view SnapOnOutputNoSelection{ "noSelection" };
static constexpr std::string_view SnapOnOutputAtBottom{ "atBottom" };
static constexpr std::string_view SnapOnOutputAlways{ "always" };

Profile::Profile() :
Profile(std::nullopt)
{
Expand All @@ -107,6 +114,7 @@ Profile::Profile(const std::optional<GUID>& guid) :
_suppressApplicationTitle{},
_historySize{ DEFAULT_HISTORY_SIZE },
_snapOnInput{ true },
_snapOnOutput{ DEFAULT_SNAP_ON_OUTPUT },
_cursorShape{ CursorStyle::Bar },
_cursorHeight{ DEFAULT_CURSOR_HEIGHT },

Expand Down Expand Up @@ -170,6 +178,7 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w
// Fill in the Terminal Setting's CoreSettings from the profile
terminalSettings.HistorySize(_historySize);
terminalSettings.SnapOnInput(_snapOnInput);
terminalSettings.SnapOnOutput(_snapOnOutput);
terminalSettings.CursorHeight(_cursorHeight);
terminalSettings.CursorShape(_cursorShape);

Expand Down Expand Up @@ -456,6 +465,12 @@ void Profile::LayerJson(const Json::Value& json)

JsonUtils::GetBool(json, SnapOnInputKey, _snapOnInput);

if (json.isMember(JsonKey(SnapOnOutputKey)))
{
auto snapOnOutput{ json[JsonKey(SnapOnOutputKey)] };
_snapOnOutput = ParseSnapOnOutput(snapOnOutput);
}

JsonUtils::GetUInt(json, CursorHeightKey, _cursorHeight);

if (json.isMember(JsonKey(CursorShapeKey)))
Expand Down Expand Up @@ -794,6 +809,77 @@ std::string_view Profile::_SerializeCloseOnExitMode(const CloseOnExitMode closeO
}
}

int32_t Profile::ParseSnapOnOutput(const Json::Value& json)
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
{
const int32_t defaultValue = static_cast<int32_t>(SnapOnOutputFlag::AtBottom) | static_cast<int32_t>(SnapOnOutputFlag::NoSelection);

if (json.isString())
{
auto snapOnOutput = json.asString();
if (snapOnOutput == SnapOnOutputNever)
{
return static_cast<int32_t>(SnapOnOutputFlag::Never);
}
else if (snapOnOutput == SnapOnOutputNoSelection)
{
return static_cast<int32_t>(SnapOnOutputFlag::NoSelection);
}
else if (snapOnOutput == SnapOnOutputAtBottom)
{
return static_cast<int32_t>(SnapOnOutputFlag::AtBottom);
}
else if (snapOnOutput == SnapOnOutputAlways)
{
return static_cast<int32_t>(SnapOnOutputFlag::Always);
}
}
else if (json.isArray())
{
int32_t result = 0;
for (const auto value : json)
{
const auto snapOnOutput = value.asString();
if (snapOnOutput == SnapOnOutputNever || snapOnOutput == SnapOnOutputAlways)
{
// these values are not accepted
// this is considered a failure to parse and we fallback to the default value instead
return defaultValue;
}
else if (snapOnOutput == SnapOnOutputNoSelection)
{
result |= static_cast<int32_t>(SnapOnOutputFlag::NoSelection);
}
else if (snapOnOutput == SnapOnOutputAtBottom)
{
result |= static_cast<int32_t>(SnapOnOutputFlag::AtBottom);
}
}
}

return defaultValue;
}

std::string_view Profile::_SerializeSnapOnOutput(SnapOnOutputFlag snapOnOutput)
{
if (snapOnOutput == SnapOnOutputFlag::Never)
{
return SnapOnOutputNever;
}
else if (snapOnOutput == SnapOnOutputFlag::Always)
{
return SnapOnOutputAlways;
}
else if (snapOnOutput == SnapOnOutputFlag::AtBottom)
{
return SnapOnOutputAtBottom;
}
else if (snapOnOutput == SnapOnOutputFlag::NoSelection)
{
return SnapOnOutputNoSelection;
}
return "";
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
}

// Method Description:
// - Helper function for converting a user-specified scrollbar state to its corresponding enum
// Arguments:
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalApp/Profile.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ class TerminalApp::Profile final
static CloseOnExitMode ParseCloseOnExitMode(const Json::Value& json);
static std::string_view _SerializeCloseOnExitMode(const CloseOnExitMode closeOnExitMode);

static int32_t ParseSnapOnOutput(const Json::Value& json);
static std::string_view _SerializeSnapOnOutput(winrt::Microsoft::Terminal::Settings::SnapOnOutputFlag snapOnOutput);

static std::string_view SerializeImageAlignment(const std::tuple<winrt::Windows::UI::Xaml::HorizontalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment> imageAlignment);
static winrt::Microsoft::Terminal::Settings::CursorStyle _ParseCursorShape(const std::wstring& cursorShapeString);
static std::wstring_view _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape);
Expand Down Expand Up @@ -145,6 +148,7 @@ class TerminalApp::Profile final
bool _suppressApplicationTitle;
int32_t _historySize;
bool _snapOnInput;
int32_t _snapOnOutput;
uint32_t _cursorHeight;
winrt::Microsoft::Terminal::Settings::CursorStyle _cursorShape;

Expand Down
42 changes: 42 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Terminal::Terminal() :
_pfnWriteInput{ nullptr },
_scrollOffset{ 0 },
_snapOnInput{ true },
_snapOnOutput{ static_cast<int>(SnapOnOutputFlag::NoSelection) | static_cast<int>(SnapOnOutputFlag::AtBottom) },
_blockSelection{ false },
_selection{ std::nullopt }
{
Expand Down Expand Up @@ -141,6 +142,8 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting

_snapOnInput = settings.SnapOnInput();

_snapOnOutput = settings.SnapOnOutput();

_wordDelimiters = settings.WordDelimiters();

_suppressApplicationTitle = settings.SuppressApplicationTitle();
Expand Down Expand Up @@ -772,10 +775,19 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
notifyScroll = true;
}

// detect if viewport was already at the bottom of the scroll history
// This is used for SnapOnOutput::AtBottom
bool viewportAtBottom = false;
if (cursor.GetPosition().Y <= _mutableViewport.BottomInclusive() - _scrollOffset)
{
viewportAtBottom = true;
}

// Update Cursor Position
cursor.SetPosition(proposedCursorPosition);

const COORD cursorPosAfter = cursor.GetPosition();
const auto scrollAmount = std::max(0, cursorPosAfter.Y - _mutableViewport.BottomInclusive());

// Move the viewport down if the cursor moved below the viewport.
if (cursorPosAfter.Y > _mutableViewport.BottomInclusive())
Expand All @@ -791,11 +803,41 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)

if (notifyScroll)
{
// scrollToOutput:
// - true: we want the visible viewport to show the new output
// - false: keep the visible viewport in its current position
bool scrollToOutput = true;

// modify scrollOffset based on SnapOnOutput value
if (_snapOnOutput == static_cast<int>(SnapOnOutputFlag::Always))
{
scrollToOutput = true;
}
else if (_snapOnOutput == static_cast<int>(SnapOnOutputFlag::Never))
{
scrollToOutput = false;
}

// IMPORTANT: we need to use && below. This allows multiple of these flags to be set
if (WI_IsFlagSet(_snapOnOutput, static_cast<int>(SnapOnOutputFlag::NoSelection)))
{
// scroll if no selection is active
scrollToOutput = scrollToOutput && !IsSelectionActive();
}
if (WI_IsFlagSet(_snapOnOutput, static_cast<int>(SnapOnOutputFlag::AtBottom)))
{
scrollToOutput = scrollToOutput && viewportAtBottom;
}

// use scrollToOutput to enforce SnapOnInput
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
_scrollOffset = scrollToOutput ? 0 : _scrollOffset + scrollAmount + newRows;

// We have to report the delta here because we might have circled the text buffer.
// That didn't change the viewport and therefore the TriggerScroll(void)
// method can't detect the delta on its own.
COORD delta{ 0, -gsl::narrow<SHORT>(newRows) };
_buffer->GetRenderTarget().TriggerScroll(&delta);

_NotifyScrollEvent();
}

Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ class Microsoft::Terminal::Core::Terminal final :
COLORREF _defaultBg;

bool _snapOnInput;
int _snapOnOutput;
bool _suppressApplicationTitle;

#pragma region Text Selection
Expand Down
9 changes: 9 additions & 0 deletions src/cascadia/TerminalSettings/ICoreSettings.idl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ namespace Microsoft.Terminal.Settings
EmptyBox
};

enum SnapOnOutputFlag
{
Never = -1,
NoSelection = 0x1,
AtBottom = 0x2,
Always = 0x4
};

interface ICoreSettings
{
UInt32 DefaultForeground;
Expand All @@ -25,6 +33,7 @@ namespace Microsoft.Terminal.Settings
Int32 RowsToScroll;

Boolean SnapOnInput;
Int32 SnapOnOutput;

UInt32 CursorColor;
CursorStyle CursorShape;
Expand Down
10 changes: 10 additions & 0 deletions src/cascadia/TerminalSettings/TerminalSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
_snapOnInput = value;
}

int32_t TerminalSettings::SnapOnOutput() noexcept
{
return _snapOnOutput;
}

void TerminalSettings::SnapOnOutput(int32_t value) noexcept
{
_snapOnOutput = value;
}

uint32_t TerminalSettings::CursorColor() noexcept
{
return _cursorColor;
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalSettings/terminalsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
void RowsToScroll(int32_t value) noexcept;
bool SnapOnInput() noexcept;
void SnapOnInput(bool value) noexcept;
int32_t SnapOnOutput() noexcept;
void SnapOnOutput(int32_t value) noexcept;
uint32_t CursorColor() noexcept;
void CursorColor(uint32_t value) noexcept;
CursorStyle CursorShape() const noexcept;
Expand Down Expand Up @@ -123,6 +125,7 @@ namespace winrt::Microsoft::Terminal::Settings::implementation
int32_t _initialCols;
int32_t _rowsToScroll;
bool _snapOnInput;
int32_t _snapOnOutput;
uint32_t _cursorColor;
Settings::CursorStyle _cursorShape;
uint32_t _cursorHeight;
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/UnitTests_TerminalCore/MockTermSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace TerminalCoreUnitTests
uint32_t DefaultForeground() { return COLOR_WHITE; }
uint32_t DefaultBackground() { return COLOR_BLACK; }
bool SnapOnInput() { return false; }
int32_t SnapOnOutput() { return DEFAULT_SNAP_ON_OUTPUT; }
uint32_t CursorColor() { return COLOR_WHITE; }
CursorStyle CursorShape() const noexcept { return CursorStyle::Vintage; }
uint32_t CursorHeight() { return 42UL; }
Expand All @@ -49,6 +50,7 @@ namespace TerminalCoreUnitTests
void DefaultForeground(uint32_t) {}
void DefaultBackground(uint32_t) {}
void SnapOnInput(bool) {}
void SnapOnOutput(int32_t) {}
void CursorColor(uint32_t) {}
void CursorShape(CursorStyle const&) noexcept {}
void CursorHeight(uint32_t) {}
Expand Down
1 change: 1 addition & 0 deletions src/inc/DefaultSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ constexpr COLORREF DEFAULT_CURSOR_COLOR = COLOR_WHITE;
constexpr COLORREF DEFAULT_CURSOR_HEIGHT = 25;

const std::wstring DEFAULT_WORD_DELIMITERS{ L" ./\\()\"'-:,.;<>~!@#$%^&*|+=[]{}~?\u2502" };
constexpr int32_t DEFAULT_SNAP_ON_OUTPUT = 0x1 | 0x2;
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
#pragma warning(pop)