Add support for "Automatic" splits (#4025)

## Summary of the Pull Request

Adds support for `auto` as a potential value for a `splitPane` keybinding's `split` argument. For example:

```json
        { "keys": [ "ctrl+shift+z" ], "command": { "action": "splitPane", "profile": "matrix", "commandline": "cmd.exe", "split":"auto" } },
```

When set to `auto`, Panes will decide which direction to split based on the available space within the terminal. If the pane is wider than it is tall, the pane will introduce a new vertical split (and vice-versa).

## References

## PR Checklist
* [x] Closes #3960
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Validation Steps Performed
Ran tests, played with it.
This commit is contained in:
Mike Griese 2019-12-19 15:47:19 -06:00 committed by msftbot[bot]
parent ae580e7b07
commit 2e26c3e0c9
5 changed files with 71 additions and 5 deletions

View file

@ -362,7 +362,9 @@ namespace TerminalAppLocalTests
{ "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical" } },
{ "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal" } },
{ "keys": ["ctrl+f"], "command": { "action": "splitPane", "split": "none" } },
{ "keys": ["ctrl+g"], "command": { "action": "splitPane" } }
{ "keys": ["ctrl+g"], "command": { "action": "splitPane" } },
{ "keys": ["ctrl+h"], "command": { "action": "splitPane", "split": "auto" } },
{ "keys": ["ctrl+i"], "command": { "action": "splitPane", "split": "foo" } }
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
@ -371,7 +373,7 @@ namespace TerminalAppLocalTests
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(7u, appKeyBindings->_keyShortcuts.size());
VERIFY_ARE_EQUAL(9u, appKeyBindings->_keyShortcuts.size());
{
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
@ -436,6 +438,24 @@ namespace TerminalAppLocalTests
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::None, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('H') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
}
{
KeyChord kc{ true, false, false, static_cast<int32_t>('I') };
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::None, realArgs.SplitStyle());
}
}
}

View file

@ -295,6 +295,7 @@ namespace winrt::TerminalApp::implementation
// TODO:GH#2550/#3475 - move these to a centralized deserializing place
static constexpr std::string_view VerticalKey{ "vertical" };
static constexpr std::string_view HorizontalKey{ "horizontal" };
static constexpr std::string_view AutomaticKey{ "auto" };
static TerminalApp::SplitState ParseSplitState(const std::string& stateString)
{
if (stateString == VerticalKey)
@ -305,6 +306,10 @@ namespace winrt::TerminalApp::implementation
{
return TerminalApp::SplitState::Horizontal;
}
else if (stateString == AutomaticKey)
{
return TerminalApp::SplitState::Automatic;
}
// default behavior for invalid data
return TerminalApp::SplitState::None;
};

View file

@ -25,6 +25,7 @@ namespace TerminalApp
enum SplitState
{
Automatic = -1,
None = 0,
Vertical = 1,
Horizontal = 2

View file

@ -935,6 +935,31 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::Split(SplitState s
return _Split(splitType, profile, control);
}
// Method Description:
// - Converts an "automatic" split type into either Vertical or Horizontal,
// based upon the current dimensions of the Pane.
// - If any of the other SplitState values are passed in, they're returned
// unmodified.
// Arguments:
// - splitType: The SplitState to attempt to convert
// Return Value:
// - None if splitType was None, otherwise one of Horizontal or Vertical
SplitState Pane::_convertAutomaticSplitState(const SplitState& splitType) const
{
// Careful here! If the pane doesn't yet have a size, these dimensions will
// be 0, and we'll always return Vertical.
if (splitType == SplitState::Automatic)
{
// If the requested split type was "auto", determine which direction to
// split based on our current dimensions
const Size actualSize{ gsl::narrow_cast<float>(_root.ActualWidth()),
gsl::narrow_cast<float>(_root.ActualHeight()) };
return actualSize.Width >= actualSize.Height ? SplitState::Vertical : SplitState::Horizontal;
}
return splitType;
}
// Method Description:
// - Determines whether the pane can be split.
// Arguments:
@ -948,7 +973,14 @@ bool Pane::_CanSplit(SplitState splitType)
const Size minSize = _GetMinSize();
if (splitType == SplitState::Vertical)
auto actualSplitType = _convertAutomaticSplitState(splitType);
if (actualSplitType == SplitState::None)
{
return false;
}
if (actualSplitType == SplitState::Vertical)
{
const auto widthMinusSeparator = actualSize.Width - CombinedPaneBorderSize;
const auto newWidth = widthMinusSeparator * Half;
@ -956,7 +988,7 @@ bool Pane::_CanSplit(SplitState splitType)
return newWidth > minSize.Width;
}
if (splitType == SplitState::Horizontal)
if (actualSplitType == SplitState::Horizontal)
{
const auto heightMinusSeparator = actualSize.Height - CombinedPaneBorderSize;
const auto newHeight = heightMinusSeparator * Half;
@ -978,6 +1010,13 @@ bool Pane::_CanSplit(SplitState splitType)
// - The two newly created Panes
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState splitType, const GUID& profile, const TermControl& control)
{
if (splitType == SplitState::None)
{
return { nullptr, nullptr };
}
auto actualSplitType = _convertAutomaticSplitState(splitType);
// Lock the create/close lock so that another operation won't concurrently
// modify our tree
std::unique_lock lock{ _createCloseLock };
@ -991,7 +1030,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState
// parent.
_gotFocusRevoker.revoke();
_splitState = splitType;
_splitState = actualSplitType;
_firstPercent = { Half };
_secondPercent = { Half };

View file

@ -120,6 +120,7 @@ private:
void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
winrt::TerminalApp::SplitState _convertAutomaticSplitState(const winrt::TerminalApp::SplitState& splitType) const;
// Function Description:
// - Returns true if the given direction can be used with the given split
// type.