Skip to content

Commit

Permalink
Add descriptions to commands (namely, snippets) (#17376)
Browse files Browse the repository at this point in the history
This adds a `"description"` property to actions. Notably, the shell
completion protocol (#3121) will now also populate that.

The suggestions UI can then use those descriptions to display an
additional tooltip with that information.

TeachingTip was kinda an abject disaster last time I tried this, so this
_isn't_ a TeachingTip. It's literally a text block.

xlinks:
* #13000
* #15845 
* #14939 - the last abandoned attempt at this
  • Loading branch information
zadjii-msft committed Jun 12, 2024
1 parent 86ba986 commit a7e2b46
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 49 deletions.
160 changes: 135 additions & 25 deletions src/cascadia/TerminalApp/SuggestionsControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <LibraryResources.h>

#include "SuggestionsControl.g.cpp"
#include "../../types/inc/utils.hpp"

using namespace winrt;
using namespace winrt::TerminalApp;
Expand All @@ -19,6 +20,8 @@ using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Microsoft::Terminal::Settings::Model;

using namespace std::chrono_literals;

namespace winrt::TerminalApp::implementation
{
SuggestionsControl::SuggestionsControl()
Expand Down Expand Up @@ -103,9 +106,7 @@ namespace winrt::TerminalApp::implementation
// stays "attached" to the cursor.
if (Visibility() == Visibility::Visible && _direction == TerminalApp::SuggestionsDirection::BottomUp)
{
auto m = this->Margin();
m.Top = (_anchor.Y - ActualHeight());
this->Margin(m);
this->_recalculateTopMargin();
}
});

Expand Down Expand Up @@ -281,9 +282,78 @@ namespace winrt::TerminalApp::implementation
{
if (const auto actionPaletteItem{ filteredCommand.Item().try_as<winrt::TerminalApp::ActionPaletteItem>() })
{
PreviewAction.raise(*this, actionPaletteItem.Command());
const auto& cmd = actionPaletteItem.Command();
PreviewAction.raise(*this, cmd);

const auto description{ cmd.Description() };

if (const auto& selected{ SelectedItem() })
{
selected.SetValue(Automation::AutomationProperties::FullDescriptionProperty(), winrt::box_value(description));
}

if (!description.empty())
{
_openTooltip(cmd);
}
else
{
// If there's no description, then just close the tooltip.
_descriptionsView().Visibility(Visibility::Collapsed);
_descriptionsBackdrop().Visibility(Visibility::Collapsed);
_recalculateTopMargin();
}
}
}
}

void SuggestionsControl::_openTooltip(Command cmd)
{
const auto description{ cmd.Description() };
if (description.empty())
{
return;
}

// Build the contents of the "tooltip" based on the description
//
// First, the title. This is just the name of the command.
_descriptionTitle().Inlines().Clear();
Documents::Run titleRun;
titleRun.Text(cmd.Name());
_descriptionTitle().Inlines().Append(titleRun);

// Now fill up the "subtitle" part of the "tooltip" with the actual
// description itself.
const auto& inlines{ _descriptionComment().Inlines() };
inlines.Clear();

// Split the filtered description on '\n`
const auto lines = ::Microsoft::Console::Utils::SplitString(description, L'\n');
// build a Run + LineBreak, and add them to the text block
for (const auto& line : lines)
{
// Trim off any `\r`'s in the string. Pwsh completions will
// frequently have these embedded.
std::wstring trimmed{ line };
trimmed.erase(std::remove(trimmed.begin(), trimmed.end(), L'\r'), trimmed.end());
if (trimmed.empty())
{
continue;
}

Documents::Run textRun;
textRun.Text(trimmed);
inlines.Append(textRun);
inlines.Append(Documents::LineBreak{});
}

// Now, make ourselves visible.
_descriptionsView().Visibility(Visibility::Visible);
_descriptionsBackdrop().Visibility(Visibility::Visible);
// and update the padding to account for our new contents.
_recalculateTopMargin();
return;
}

void SuggestionsControl::_previewKeyDownHandler(const IInspectable& /*sender*/,
Expand Down Expand Up @@ -1020,16 +1090,71 @@ namespace winrt::TerminalApp::implementation
void SuggestionsControl::_setDirection(TerminalApp::SuggestionsDirection direction)
{
_direction = direction;

// We need to move either the list of suggestions, or the tooltip, to
// the top of the stack panel (depending on the layout).
Grid controlToMoveToTop = nullptr;

if (_direction == TerminalApp::SuggestionsDirection::TopDown)
{
Controls::Grid::SetRow(_searchBox(), 0);
controlToMoveToTop = _backdrop();
}
else // BottomUp
{
Controls::Grid::SetRow(_searchBox(), 4);
controlToMoveToTop = _descriptionsBackdrop();
}

assert(controlToMoveToTop);
const auto& children{ _listAndDescriptionStack().Children() };
uint32_t index;
if (children.IndexOf(controlToMoveToTop, index))
{
children.Move(index, 0);
}
}

void SuggestionsControl::_recalculateTopMargin()
{
auto currentMargin = Margin();
// Call Measure() on the descriptions backdrop, so that it gets it's new
// DesiredSize for this new description text.
//
// If you forget this, then we _probably_ weren't laid out since
// updating that text, and the ActualHeight will be the _last_
// description's height.
_descriptionsBackdrop().Measure({
static_cast<float>(ActualWidth()),
static_cast<float>(ActualHeight()),
});

// Now, position vertically.
if (_direction == TerminalApp::SuggestionsDirection::TopDown)
{
// The control should open right below the cursor, with the list
// extending below. This is easy, we can just use the cursor as the
// origin (more or less)
currentMargin.Top = (_anchor.Y);
}
else
{
// Bottom Up.

// This is wackier, because we need to calculate the offset upwards
// from our anchor. So we need to get the size of our elements:
const auto backdropHeight = _backdrop().ActualHeight();
const auto descriptionDesiredHeight = _descriptionsBackdrop().Visibility() == Visibility::Visible ?
_descriptionsBackdrop().DesiredSize().Height :
0;

const auto marginTop = (_anchor.Y - backdropHeight - descriptionDesiredHeight);

currentMargin.Top = marginTop;
}
Margin(currentMargin);
}

void SuggestionsControl::Open(TerminalApp::SuggestionsMode mode,
const Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command>& commands,
winrt::hstring filter,
Expand All @@ -1047,9 +1172,8 @@ namespace winrt::TerminalApp::implementation
_anchor = anchor;
_space = space;

const til::size actualSize{ til::math::rounding, ActualWidth(), ActualHeight() };
// Is there space in the window below the cursor to open the menu downwards?
const bool canOpenDownwards = (_anchor.Y + characterHeight + actualSize.height) < space.Height;
const bool canOpenDownwards = (_anchor.Y + characterHeight + ActualHeight()) < space.Height;
_setDirection(canOpenDownwards ? TerminalApp::SuggestionsDirection::TopDown :
TerminalApp::SuggestionsDirection::BottomUp);
// Set the anchor below by a character height
Expand All @@ -1063,26 +1187,13 @@ namespace winrt::TerminalApp::implementation
const auto proposedX = gsl::narrow_cast<int>(_anchor.X - 40);
// If the control is too wide to fit in the window, clamp it fit inside
// the window.
const auto maxX = gsl::narrow_cast<int>(space.Width - actualSize.width);
const auto maxX = gsl::narrow_cast<int>(space.Width - ActualWidth());
const auto clampedX = std::clamp(proposedX, 0, maxX);

// Create a thickness for the new margins
auto newMargin = Windows::UI::Xaml::ThicknessHelper::FromLengths(clampedX, 0, 0, 0);
// Now, position vertically.
if (_direction == TerminalApp::SuggestionsDirection::TopDown)
{
// The control should open right below the cursor, with the list
// extending below. This is easy, we can just use the cursor as the
// origin (more or less)
newMargin.Top = (_anchor.Y);
}
else
{
// Position at the cursor. The suggestions UI itself will maintain
// its own offset such that it's always above its origin
newMargin.Top = (_anchor.Y - actualSize.height);
}
Margin(newMargin);
// Create a thickness for the new margins. This will set the left, then
// we'll go update the top separately
Margin(Windows::UI::Xaml::ThicknessHelper::FromLengths(clampedX, 0, 0, 0));
_recalculateTopMargin();

_searchBox().Text(filter);

Expand All @@ -1099,5 +1210,4 @@ namespace winrt::TerminalApp::implementation
// selection starting at the end of the string.
_searchBox().Select(filter.size(), 0);
}

}
4 changes: 3 additions & 1 deletion src/cascadia/TerminalApp/SuggestionsControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ namespace winrt::TerminalApp::implementation
void _close();
void _dismissPalette();

void _recalculateTopMargin();

void _filterTextChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
void _previewKeyDownHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _keyUpHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
Expand All @@ -110,6 +112,7 @@ namespace winrt::TerminalApp::implementation
void _listItemClicked(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::ItemClickEventArgs& e);
void _listItemSelectionChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& e);
void _selectedCommandChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
void _openTooltip(Microsoft::Terminal::Settings::Model::Command cmd);

void _moveBackButtonClicked(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs&);
void _updateCurrentNestedCommands(const winrt::Microsoft::Terminal::Settings::Model::Command& parentCommand);
Expand All @@ -121,7 +124,6 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _commandsToFilter();
std::wstring _getTrimmedInput();
uint32_t _getNumVisibleItems();

friend class TerminalAppLocalTests::TabTests;
};
}
Expand Down
72 changes: 50 additions & 22 deletions src/cascadia/TerminalApp/SuggestionsControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,23 +102,10 @@
</ResourceDictionary>
</UserControl.Resources>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>

<Grid.RowDefinitions>
<RowDefinition Height="8*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>

<StackPanel x:Name="_listAndDescriptionStack"
Orientation="Vertical">
<Grid x:Name="_backdrop"
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Grid.ColumnSpan="3"
MinWidth="300"
MaxWidth="300"
MaxHeight="300"
Margin="0"
Expand All @@ -134,16 +121,16 @@
Translation="0,0,32">

<Grid.RowDefinitions>
<!-- 0: Top-down _searchBox -->
<RowDefinition Height="Auto" />
<!-- Top-down _searchBox -->
<!-- 1: Top-down ParentCommandName -->
<RowDefinition Height="Auto" />
<!-- Top-down ParentCommandName -->
<!-- 2: Top-down UNUSED???????? -->
<RowDefinition Height="Auto" />
<!-- Top-down UNUSED???????? -->
<!-- 3: _filteredActionsView -->
<RowDefinition Height="*" />
<!-- _filteredActionsView -->
<!-- 4: bottom-up _searchBox -->
<RowDefinition Height="Auto" />
<!-- bottom-up _searchBox -->
</Grid.RowDefinitions>

<TextBox x:Name="_searchBox"
Expand Down Expand Up @@ -206,8 +193,49 @@
SelectionMode="Single"
Style="{StaticResource NoAnimationsPlease}" />


</Grid>

<Grid x:Name="_descriptionsBackdrop"
MaxWidth="300"
Margin="0,6,0,6"
Padding="0,4,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{ThemeResource FlyoutPresenterBackground}"
BorderBrush="{ThemeResource FlyoutBorderThemeBrush}"
BorderThickness="{ThemeResource FlyoutBorderThemeThickness}"
CornerRadius="{ThemeResource OverlayCornerRadius}"
PointerPressed="_backdropPointerPressed"
Shadow="{StaticResource SharedShadow}"
Translation="0,0,32">

<Grid.RowDefinitions>
<!-- 0: descriptions view -->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<StackPanel x:Name="_descriptionsView"
Grid.Row="0"
Margin="8,0,8,8"
Orientation="Vertical"
Visibility="Collapsed">
<TextBlock x:Name="_descriptionTitle"
FontSize="14"
FontWeight="Bold"
IsTextSelectionEnabled="True"
TextWrapping="WrapWholeWords" />
<ScrollViewer MaxHeight="64"
VerticalScrollBarVisibility="Visible"
VerticalScrollMode="Enabled"
Visibility="Visible">
<TextBlock x:Name="_descriptionComment"
IsTextSelectionEnabled="True"
TextWrapping="WrapWholeWords" />
</ScrollViewer>
</StackPanel>

</Grid>

</Grid>
</StackPanel>
</UserControl>
Loading

0 comments on commit a7e2b46

Please sign in to comment.