Skip to content

Commit a7e2b46

Browse files
authored
Add descriptions to commands (namely, snippets) (#17376)
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
1 parent 86ba986 commit a7e2b46

File tree

6 files changed

+202
-49
lines changed

6 files changed

+202
-49
lines changed

src/cascadia/TerminalApp/SuggestionsControl.cpp

Lines changed: 135 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <LibraryResources.h>
99

1010
#include "SuggestionsControl.g.cpp"
11+
#include "../../types/inc/utils.hpp"
1112

1213
using namespace winrt;
1314
using namespace winrt::TerminalApp;
@@ -19,6 +20,8 @@ using namespace winrt::Windows::Foundation;
1920
using namespace winrt::Windows::Foundation::Collections;
2021
using namespace winrt::Microsoft::Terminal::Settings::Model;
2122

23+
using namespace std::chrono_literals;
24+
2225
namespace winrt::TerminalApp::implementation
2326
{
2427
SuggestionsControl::SuggestionsControl()
@@ -103,9 +106,7 @@ namespace winrt::TerminalApp::implementation
103106
// stays "attached" to the cursor.
104107
if (Visibility() == Visibility::Visible && _direction == TerminalApp::SuggestionsDirection::BottomUp)
105108
{
106-
auto m = this->Margin();
107-
m.Top = (_anchor.Y - ActualHeight());
108-
this->Margin(m);
109+
this->_recalculateTopMargin();
109110
}
110111
});
111112

@@ -281,9 +282,78 @@ namespace winrt::TerminalApp::implementation
281282
{
282283
if (const auto actionPaletteItem{ filteredCommand.Item().try_as<winrt::TerminalApp::ActionPaletteItem>() })
283284
{
284-
PreviewAction.raise(*this, actionPaletteItem.Command());
285+
const auto& cmd = actionPaletteItem.Command();
286+
PreviewAction.raise(*this, cmd);
287+
288+
const auto description{ cmd.Description() };
289+
290+
if (const auto& selected{ SelectedItem() })
291+
{
292+
selected.SetValue(Automation::AutomationProperties::FullDescriptionProperty(), winrt::box_value(description));
293+
}
294+
295+
if (!description.empty())
296+
{
297+
_openTooltip(cmd);
298+
}
299+
else
300+
{
301+
// If there's no description, then just close the tooltip.
302+
_descriptionsView().Visibility(Visibility::Collapsed);
303+
_descriptionsBackdrop().Visibility(Visibility::Collapsed);
304+
_recalculateTopMargin();
305+
}
306+
}
307+
}
308+
}
309+
310+
void SuggestionsControl::_openTooltip(Command cmd)
311+
{
312+
const auto description{ cmd.Description() };
313+
if (description.empty())
314+
{
315+
return;
316+
}
317+
318+
// Build the contents of the "tooltip" based on the description
319+
//
320+
// First, the title. This is just the name of the command.
321+
_descriptionTitle().Inlines().Clear();
322+
Documents::Run titleRun;
323+
titleRun.Text(cmd.Name());
324+
_descriptionTitle().Inlines().Append(titleRun);
325+
326+
// Now fill up the "subtitle" part of the "tooltip" with the actual
327+
// description itself.
328+
const auto& inlines{ _descriptionComment().Inlines() };
329+
inlines.Clear();
330+
331+
// Split the filtered description on '\n`
332+
const auto lines = ::Microsoft::Console::Utils::SplitString(description, L'\n');
333+
// build a Run + LineBreak, and add them to the text block
334+
for (const auto& line : lines)
335+
{
336+
// Trim off any `\r`'s in the string. Pwsh completions will
337+
// frequently have these embedded.
338+
std::wstring trimmed{ line };
339+
trimmed.erase(std::remove(trimmed.begin(), trimmed.end(), L'\r'), trimmed.end());
340+
if (trimmed.empty())
341+
{
342+
continue;
285343
}
344+
345+
Documents::Run textRun;
346+
textRun.Text(trimmed);
347+
inlines.Append(textRun);
348+
inlines.Append(Documents::LineBreak{});
286349
}
350+
351+
// Now, make ourselves visible.
352+
_descriptionsView().Visibility(Visibility::Visible);
353+
_descriptionsBackdrop().Visibility(Visibility::Visible);
354+
// and update the padding to account for our new contents.
355+
_recalculateTopMargin();
356+
return;
287357
}
288358

289359
void SuggestionsControl::_previewKeyDownHandler(const IInspectable& /*sender*/,
@@ -1020,16 +1090,71 @@ namespace winrt::TerminalApp::implementation
10201090
void SuggestionsControl::_setDirection(TerminalApp::SuggestionsDirection direction)
10211091
{
10221092
_direction = direction;
1093+
1094+
// We need to move either the list of suggestions, or the tooltip, to
1095+
// the top of the stack panel (depending on the layout).
1096+
Grid controlToMoveToTop = nullptr;
1097+
10231098
if (_direction == TerminalApp::SuggestionsDirection::TopDown)
10241099
{
10251100
Controls::Grid::SetRow(_searchBox(), 0);
1101+
controlToMoveToTop = _backdrop();
10261102
}
10271103
else // BottomUp
10281104
{
10291105
Controls::Grid::SetRow(_searchBox(), 4);
1106+
controlToMoveToTop = _descriptionsBackdrop();
1107+
}
1108+
1109+
assert(controlToMoveToTop);
1110+
const auto& children{ _listAndDescriptionStack().Children() };
1111+
uint32_t index;
1112+
if (children.IndexOf(controlToMoveToTop, index))
1113+
{
1114+
children.Move(index, 0);
10301115
}
10311116
}
10321117

1118+
void SuggestionsControl::_recalculateTopMargin()
1119+
{
1120+
auto currentMargin = Margin();
1121+
// Call Measure() on the descriptions backdrop, so that it gets it's new
1122+
// DesiredSize for this new description text.
1123+
//
1124+
// If you forget this, then we _probably_ weren't laid out since
1125+
// updating that text, and the ActualHeight will be the _last_
1126+
// description's height.
1127+
_descriptionsBackdrop().Measure({
1128+
static_cast<float>(ActualWidth()),
1129+
static_cast<float>(ActualHeight()),
1130+
});
1131+
1132+
// Now, position vertically.
1133+
if (_direction == TerminalApp::SuggestionsDirection::TopDown)
1134+
{
1135+
// The control should open right below the cursor, with the list
1136+
// extending below. This is easy, we can just use the cursor as the
1137+
// origin (more or less)
1138+
currentMargin.Top = (_anchor.Y);
1139+
}
1140+
else
1141+
{
1142+
// Bottom Up.
1143+
1144+
// This is wackier, because we need to calculate the offset upwards
1145+
// from our anchor. So we need to get the size of our elements:
1146+
const auto backdropHeight = _backdrop().ActualHeight();
1147+
const auto descriptionDesiredHeight = _descriptionsBackdrop().Visibility() == Visibility::Visible ?
1148+
_descriptionsBackdrop().DesiredSize().Height :
1149+
0;
1150+
1151+
const auto marginTop = (_anchor.Y - backdropHeight - descriptionDesiredHeight);
1152+
1153+
currentMargin.Top = marginTop;
1154+
}
1155+
Margin(currentMargin);
1156+
}
1157+
10331158
void SuggestionsControl::Open(TerminalApp::SuggestionsMode mode,
10341159
const Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command>& commands,
10351160
winrt::hstring filter,
@@ -1047,9 +1172,8 @@ namespace winrt::TerminalApp::implementation
10471172
_anchor = anchor;
10481173
_space = space;
10491174

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

1069-
// Create a thickness for the new margins
1070-
auto newMargin = Windows::UI::Xaml::ThicknessHelper::FromLengths(clampedX, 0, 0, 0);
1071-
// Now, position vertically.
1072-
if (_direction == TerminalApp::SuggestionsDirection::TopDown)
1073-
{
1074-
// The control should open right below the cursor, with the list
1075-
// extending below. This is easy, we can just use the cursor as the
1076-
// origin (more or less)
1077-
newMargin.Top = (_anchor.Y);
1078-
}
1079-
else
1080-
{
1081-
// Position at the cursor. The suggestions UI itself will maintain
1082-
// its own offset such that it's always above its origin
1083-
newMargin.Top = (_anchor.Y - actualSize.height);
1084-
}
1085-
Margin(newMargin);
1193+
// Create a thickness for the new margins. This will set the left, then
1194+
// we'll go update the top separately
1195+
Margin(Windows::UI::Xaml::ThicknessHelper::FromLengths(clampedX, 0, 0, 0));
1196+
_recalculateTopMargin();
10861197

10871198
_searchBox().Text(filter);
10881199

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

src/cascadia/TerminalApp/SuggestionsControl.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ namespace winrt::TerminalApp::implementation
9898
void _close();
9999
void _dismissPalette();
100100

101+
void _recalculateTopMargin();
102+
101103
void _filterTextChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
102104
void _previewKeyDownHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
103105
void _keyUpHandler(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
@@ -110,6 +112,7 @@ namespace winrt::TerminalApp::implementation
110112
void _listItemClicked(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::ItemClickEventArgs& e);
111113
void _listItemSelectionChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Controls::SelectionChangedEventArgs& e);
112114
void _selectedCommandChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args);
115+
void _openTooltip(Microsoft::Terminal::Settings::Model::Command cmd);
113116

114117
void _moveBackButtonClicked(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs&);
115118
void _updateCurrentNestedCommands(const winrt::Microsoft::Terminal::Settings::Model::Command& parentCommand);
@@ -121,7 +124,6 @@ namespace winrt::TerminalApp::implementation
121124
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _commandsToFilter();
122125
std::wstring _getTrimmedInput();
123126
uint32_t _getNumVisibleItems();
124-
125127
friend class TerminalAppLocalTests::TabTests;
126128
};
127129
}

src/cascadia/TerminalApp/SuggestionsControl.xaml

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -102,23 +102,10 @@
102102
</ResourceDictionary>
103103
</UserControl.Resources>
104104

105-
<Grid>
106-
<Grid.ColumnDefinitions>
107-
<ColumnDefinition Width="2*" />
108-
<ColumnDefinition Width="6*" />
109-
<ColumnDefinition Width="2*" />
110-
</Grid.ColumnDefinitions>
111-
112-
<Grid.RowDefinitions>
113-
<RowDefinition Height="8*" />
114-
<RowDefinition Height="2*" />
115-
</Grid.RowDefinitions>
116-
105+
<StackPanel x:Name="_listAndDescriptionStack"
106+
Orientation="Vertical">
117107
<Grid x:Name="_backdrop"
118-
Grid.Row="0"
119-
Grid.RowSpan="2"
120-
Grid.Column="0"
121-
Grid.ColumnSpan="3"
108+
MinWidth="300"
122109
MaxWidth="300"
123110
MaxHeight="300"
124111
Margin="0"
@@ -134,16 +121,16 @@
134121
Translation="0,0,32">
135122

136123
<Grid.RowDefinitions>
124+
<!-- 0: Top-down _searchBox -->
137125
<RowDefinition Height="Auto" />
138-
<!-- Top-down _searchBox -->
126+
<!-- 1: Top-down ParentCommandName -->
139127
<RowDefinition Height="Auto" />
140-
<!-- Top-down ParentCommandName -->
128+
<!-- 2: Top-down UNUSED???????? -->
141129
<RowDefinition Height="Auto" />
142-
<!-- Top-down UNUSED???????? -->
130+
<!-- 3: _filteredActionsView -->
143131
<RowDefinition Height="*" />
144-
<!-- _filteredActionsView -->
132+
<!-- 4: bottom-up _searchBox -->
145133
<RowDefinition Height="Auto" />
146-
<!-- bottom-up _searchBox -->
147134
</Grid.RowDefinitions>
148135

149136
<TextBox x:Name="_searchBox"
@@ -206,8 +193,49 @@
206193
SelectionMode="Single"
207194
Style="{StaticResource NoAnimationsPlease}" />
208195

196+
209197
</Grid>
210198

199+
<Grid x:Name="_descriptionsBackdrop"
200+
MaxWidth="300"
201+
Margin="0,6,0,6"
202+
Padding="0,4,0,0"
203+
HorizontalAlignment="Stretch"
204+
VerticalAlignment="Stretch"
205+
Background="{ThemeResource FlyoutPresenterBackground}"
206+
BorderBrush="{ThemeResource FlyoutBorderThemeBrush}"
207+
BorderThickness="{ThemeResource FlyoutBorderThemeThickness}"
208+
CornerRadius="{ThemeResource OverlayCornerRadius}"
209+
PointerPressed="_backdropPointerPressed"
210+
Shadow="{StaticResource SharedShadow}"
211+
Translation="0,0,32">
212+
213+
<Grid.RowDefinitions>
214+
<!-- 0: descriptions view -->
215+
<RowDefinition Height="Auto" />
216+
</Grid.RowDefinitions>
217+
218+
<StackPanel x:Name="_descriptionsView"
219+
Grid.Row="0"
220+
Margin="8,0,8,8"
221+
Orientation="Vertical"
222+
Visibility="Collapsed">
223+
<TextBlock x:Name="_descriptionTitle"
224+
FontSize="14"
225+
FontWeight="Bold"
226+
IsTextSelectionEnabled="True"
227+
TextWrapping="WrapWholeWords" />
228+
<ScrollViewer MaxHeight="64"
229+
VerticalScrollBarVisibility="Visible"
230+
VerticalScrollMode="Enabled"
231+
Visibility="Visible">
232+
<TextBlock x:Name="_descriptionComment"
233+
IsTextSelectionEnabled="True"
234+
TextWrapping="WrapWholeWords" />
235+
</ScrollViewer>
236+
</StackPanel>
237+
238+
</Grid>
211239

212-
</Grid>
240+
</StackPanel>
213241
</UserControl>

0 commit comments

Comments
 (0)