Skip to content

Commit 5460324

Browse files
committed
PRE-MERGE microsoft#16513 Add ability to save input action from command line
2 parents a2adcd3 + 3f15d6e commit 5460324

19 files changed

+358
-33
lines changed

src/cascadia/TerminalApp/AppActionHandlers.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,54 @@ namespace winrt::TerminalApp::implementation
12641264
}
12651265
}
12661266

1267+
void TerminalPage::_HandleSaveTask(const IInspectable& /*sender*/,
1268+
const ActionEventArgs& args)
1269+
{
1270+
if (Feature_SaveTask::IsEnabled())
1271+
{
1272+
if (args)
1273+
{
1274+
if (const auto& realArgs = args.ActionArgs().try_as<SaveTaskArgs>())
1275+
{
1276+
if (realArgs.Commandline().empty())
1277+
{
1278+
if (const auto termControl{ _GetActiveControl() })
1279+
{
1280+
if (termControl.HasSelection())
1281+
{
1282+
const auto selections{ termControl.SelectedText(true) };
1283+
const auto selection = std::accumulate(selections.begin(), selections.end(), std::wstring());
1284+
realArgs.Commandline(selection);
1285+
}
1286+
}
1287+
}
1288+
1289+
try
1290+
{
1291+
winrt::Microsoft::Terminal::Control::KeyChord keyChord = nullptr;
1292+
if (!realArgs.KeyChord().empty())
1293+
{
1294+
keyChord = KeyChordSerialization::FromString(winrt::to_hstring(realArgs.KeyChord()));
1295+
}
1296+
_settings.GlobalSettings().ActionMap().AddSendInputAction(realArgs.Name(), realArgs.Commandline(), keyChord);
1297+
_settings.WriteSettingsToDisk();
1298+
ActionSaved(realArgs.Commandline(), realArgs.Name(), realArgs.KeyChord());
1299+
}
1300+
catch (const winrt::hresult_error& ex)
1301+
{
1302+
auto code = ex.code();
1303+
auto message = ex.message();
1304+
ActionSaveFailed(message);
1305+
args.Handled(true);
1306+
return;
1307+
}
1308+
1309+
args.Handled(true);
1310+
}
1311+
}
1312+
}
1313+
}
1314+
12671315
void TerminalPage::_HandleSelectCommand(const IInspectable& /*sender*/,
12681316
const ActionEventArgs& args)
12691317
{

src/cascadia/TerminalApp/AppCommandlineArgs.cpp

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ void AppCommandlineArgs::_buildParser()
209209
_buildMovePaneParser();
210210
_buildSwapPaneParser();
211211
_buildFocusPaneParser();
212+
_buildSaveParser();
212213
}
213214

214215
// Method Description:
@@ -537,6 +538,72 @@ void AppCommandlineArgs::_buildFocusPaneParser()
537538
setupSubcommand(_focusPaneShort);
538539
}
539540

541+
void AppCommandlineArgs::_buildSaveParser()
542+
{
543+
_saveCommand = _app.add_subcommand("x-save", RS_A(L"SaveActionDesc"));
544+
545+
auto setupSubcommand = [this](auto* subcommand) {
546+
subcommand->add_option("--name,-n", _saveInputName, RS_A(L"SaveActionArgDesc"));
547+
subcommand->add_option("--keychord,-k", _keyChordOption, RS_A(L"KeyChordArgDesc"));
548+
subcommand->add_option("command,", _commandline, RS_A(L"CmdCommandArgDesc"));
549+
subcommand->positionals_at_end(true);
550+
551+
// When ParseCommand is called, if this subcommand was provided, this
552+
// callback function will be triggered on the same thread. We can be sure
553+
// that `this` will still be safe - this function just lets us know this
554+
// command was parsed.
555+
subcommand->callback([&, this]() {
556+
// Build the NewTab action from the values we've parsed on the commandline.
557+
ActionAndArgs saveAction{};
558+
saveAction.Action(ShortcutAction::SaveTask);
559+
// _getNewTerminalArgs MUST be called before parsing any other options,
560+
// as it might clear those options while finding the commandline
561+
SaveTaskArgs args{};
562+
563+
if (!_commandline.empty())
564+
{
565+
std::ostringstream cmdlineBuffer;
566+
567+
for (const auto& arg : _commandline)
568+
{
569+
if (cmdlineBuffer.tellp() != 0)
570+
{
571+
// If there's already something in here, prepend a space
572+
cmdlineBuffer << ' ';
573+
}
574+
575+
if (arg.find(" ") != std::string::npos)
576+
{
577+
cmdlineBuffer << '"' << arg << '"';
578+
}
579+
else
580+
{
581+
cmdlineBuffer << arg;
582+
}
583+
}
584+
585+
args.Commandline(winrt::to_hstring(cmdlineBuffer.str()));
586+
}
587+
588+
if (!_keyChordOption.empty())
589+
{
590+
args.KeyChord(winrt::to_hstring(_keyChordOption));
591+
}
592+
593+
if (!_saveInputName.empty())
594+
{
595+
winrt::hstring hString = winrt::to_hstring(_saveInputName);
596+
args.Name(hString);
597+
}
598+
599+
saveAction.Args(args);
600+
_startupActions.push_back(saveAction);
601+
});
602+
};
603+
604+
setupSubcommand(_saveCommand);
605+
}
606+
540607
// Method Description:
541608
// - Add the `NewTerminalArgs` parameters to the given subcommand. This enables
542609
// that subcommand to support all the properties in a NewTerminalArgs.
@@ -710,7 +777,8 @@ bool AppCommandlineArgs::_noCommandsProvided()
710777
*_focusPaneCommand ||
711778
*_focusPaneShort ||
712779
*_newPaneShort.subcommand ||
713-
*_newPaneCommand.subcommand);
780+
*_newPaneCommand.subcommand ||
781+
*_saveCommand);
714782
}
715783

716784
// Method Description:

src/cascadia/TerminalApp/AppCommandlineArgs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class TerminalApp::AppCommandlineArgs final
9393
CLI::App* _swapPaneCommand;
9494
CLI::App* _focusPaneCommand;
9595
CLI::App* _focusPaneShort;
96+
CLI::App* _saveCommand;
9697

9798
// Are you adding a new sub-command? Make sure to update _noCommandsProvided!
9899

@@ -123,6 +124,8 @@ class TerminalApp::AppCommandlineArgs final
123124
bool _focusPrevTab{ false };
124125

125126
int _focusPaneTarget{ -1 };
127+
std::string _saveInputName;
128+
std::string _keyChordOption;
126129
// Are you adding more args here? Make sure to reset them in _resetStateToDefault
127130

128131
const Commandline* _currentCommandline{ nullptr };
@@ -141,6 +144,7 @@ class TerminalApp::AppCommandlineArgs final
141144
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs _getNewTerminalArgs(NewTerminalSubcommand& subcommand);
142145
void _addNewTerminalArgs(NewTerminalSubcommand& subcommand);
143146
void _buildParser();
147+
void _buildSaveParser();
144148
void _buildNewTabParser();
145149
void _buildSplitPaneParser();
146150
void _buildFocusTabParser();

src/cascadia/TerminalApp/Resources/en-US/Resources.resw

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,15 @@
288288
<data name="CmdCommandArgDesc" xml:space="preserve">
289289
<value>An optional command, with arguments, to be spawned in the new tab or pane</value>
290290
</data>
291+
<data name="SaveActionDesc" xml:space="preserve">
292+
<value>Save command line as input action</value>
293+
</data>
294+
<data name="SaveActionArgDesc" xml:space="preserve">
295+
<value>An optional argument</value>
296+
</data>
297+
<data name="KeyChordArgDesc" xml:space="preserve">
298+
<value>An optional argument</value>
299+
</data>
291300
<data name="CmdFocusTabDesc" xml:space="preserve">
292301
<value>Move focus to another tab</value>
293302
</data>
@@ -898,4 +907,10 @@
898907
<data name="RestartConnectionToolTip" xml:space="preserve">
899908
<value>Restart the active pane connection</value>
900909
</data>
910+
<data name="ActionSavedToast.Title" xml:space="preserve">
911+
<value>Action saved</value>
912+
</data>
913+
<data name="ActionSaveFailedToast.Title" xml:space="preserve">
914+
<value>Action save failed</value>
915+
</data>
901916
</root>

src/cascadia/TerminalApp/TerminalAppLib.vcxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,9 @@
357357
</ItemGroup>
358358
<!-- ========================= Misc Files ======================== -->
359359
<ItemGroup>
360-
<PRIResource Include="Resources\en-US\Resources.resw" />
360+
<PRIResource Include="Resources\en-US\Resources.resw">
361+
<SubType>Designer</SubType>
362+
</PRIResource>
361363
<PRIResource Include="Resources\en-US\ContextMenu.resw" />
362364
<OCResourceDirectory Include="Resources" />
363365
</ItemGroup>
@@ -466,10 +468,8 @@
466468
</ItemDefinitionGroup>
467469
<!-- ========================= Globals ======================== -->
468470
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
469-
470471
<!-- This -must- go after cppwinrt.build.post.props because that includes many VS-provided props including appcontainer.common.props, which stomps on what cppwinrt.targets did. -->
471472
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
472-
473473
<!--
474474
By default, the PRI file will contain resource paths beginning with the
475475
project name. Since we enabled XBF embedding, this *also* includes App.xbf.
@@ -490,4 +490,4 @@
490490
</ItemGroup>
491491
</Target>
492492
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
493-
</Project>
493+
</Project>

src/cascadia/TerminalApp/TerminalPage.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4185,6 +4185,66 @@ namespace winrt::TerminalApp::implementation
41854185
}
41864186
}
41874187

4188+
winrt::fire_and_forget TerminalPage::ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord)
4189+
{
4190+
auto weakThis{ get_weak() };
4191+
co_await wil::resume_foreground(Dispatcher());
4192+
if (auto page{ weakThis.get() })
4193+
{
4194+
// If we haven't ever loaded the TeachingTip, then do so now and
4195+
// create the toast for it.
4196+
if (page->_actionSavedToast == nullptr)
4197+
{
4198+
if (auto tip{ page->FindName(L"ActionSavedToast").try_as<MUX::Controls::TeachingTip>() })
4199+
{
4200+
page->_actionSavedToast = std::make_shared<Toast>(tip);
4201+
// Make sure to use the weak ref when setting up this
4202+
// callback.
4203+
tip.Closed({ page->get_weak(), &TerminalPage::_FocusActiveControl });
4204+
}
4205+
}
4206+
_UpdateTeachingTipTheme(ActionSavedToast().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
4207+
4208+
SavedActionName(name);
4209+
SavedActionKeyChord(keyChord);
4210+
SavedActionCommandLine(input);
4211+
4212+
if (page->_actionSavedToast != nullptr)
4213+
{
4214+
page->_actionSavedToast->Open();
4215+
}
4216+
}
4217+
}
4218+
4219+
winrt::fire_and_forget TerminalPage::ActionSaveFailed(winrt::hstring message)
4220+
{
4221+
auto weakThis{ get_weak() };
4222+
co_await wil::resume_foreground(Dispatcher());
4223+
if (auto page{ weakThis.get() })
4224+
{
4225+
// If we haven't ever loaded the TeachingTip, then do so now and
4226+
// create the toast for it.
4227+
if (page->_actionSaveFailedToast == nullptr)
4228+
{
4229+
if (auto tip{ page->FindName(L"ActionSaveFailedToast").try_as<MUX::Controls::TeachingTip>() })
4230+
{
4231+
page->_actionSaveFailedToast = std::make_shared<Toast>(tip);
4232+
// Make sure to use the weak ref when setting up this
4233+
// callback.
4234+
tip.Closed({ page->get_weak(), &TerminalPage::_FocusActiveControl });
4235+
}
4236+
}
4237+
_UpdateTeachingTipTheme(ActionSaveFailedToast().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
4238+
4239+
ActionSaveFailedMessage().Text(message);
4240+
4241+
if (page->_actionSaveFailedToast != nullptr)
4242+
{
4243+
page->_actionSaveFailedToast->Open();
4244+
}
4245+
}
4246+
}
4247+
41884248
// Method Description:
41894249
// - Called when an attempt to rename the window has failed. This will open
41904250
// the toast displaying a message to the user that the attempt to rename

src/cascadia/TerminalApp/TerminalPage.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ namespace winrt::TerminalApp::implementation
146146
winrt::hstring KeyboardServiceDisabledText();
147147

148148
winrt::fire_and_forget IdentifyWindow();
149+
winrt::fire_and_forget ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord);
150+
winrt::fire_and_forget ActionSaveFailed(winrt::hstring message);
149151
winrt::fire_and_forget RenameFailed();
150152
winrt::fire_and_forget ShowTerminalWorkingDirectory();
151153

@@ -199,6 +201,10 @@ namespace winrt::TerminalApp::implementation
199201
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, PropertyChanged.raise, nullptr);
200202
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, FrameBrush, PropertyChanged.raise, nullptr);
201203

204+
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionName, PropertyChanged.raise, L"");
205+
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionKeyChord, PropertyChanged.raise, L"");
206+
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionCommandLine, PropertyChanged.raise, L"");
207+
202208
private:
203209
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
204210
std::optional<HWND> _hostingHwnd;
@@ -258,6 +264,8 @@ namespace winrt::TerminalApp::implementation
258264
bool _isEmbeddingInboundListener{ false };
259265

260266
std::shared_ptr<Toast> _windowIdToast{ nullptr };
267+
std::shared_ptr<Toast> _actionSavedToast{ nullptr };
268+
std::shared_ptr<Toast> _actionSaveFailedToast{ nullptr };
261269
std::shared_ptr<Toast> _windowRenameFailedToast{ nullptr };
262270
std::shared_ptr<Toast> _windowCwdToast{ nullptr };
263271

src/cascadia/TerminalApp/TerminalPage.idl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ namespace TerminalApp
7272
void IdentifyWindow();
7373
void RenameFailed();
7474

75+
String SavedActionName { get; };
76+
String SavedActionKeyChord { get; };
77+
String SavedActionCommandLine { get; };
78+
7579
// We cannot use the default XAML APIs because we want to make sure
7680
// that there's only one application-global dialog visible at a time,
7781
// and because of GH#5224.

src/cascadia/TerminalApp/TerminalPage.xaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
99
xmlns:local="using:TerminalApp"
1010
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
11+
xmlns:mtu="using:Microsoft.Terminal.UI"
1112
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
1213
Background="Transparent"
1314
mc:Ignorable="d">
@@ -204,5 +205,43 @@
204205
Title="{x:Bind WindowProperties.VirtualWorkingDirectory, Mode=OneWay}"
205206
x:Load="False"
206207
IsLightDismissEnabled="True" />
208+
209+
<mux:TeachingTip x:Name="ActionSavedToast"
210+
x:Uid="ActionSavedToast"
211+
Title="Action Saved"
212+
HorizontalAlignment="Stretch"
213+
x:Load="False"
214+
IsLightDismissEnabled="True">
215+
<mux:TeachingTip.Content>
216+
<StackPanel HorizontalAlignment="Stretch"
217+
Orientation="Vertical">
218+
<TextBlock x:Name="ActionSavedNameText"
219+
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionName), Mode=OneWay}">
220+
<Run Text="Name: " />
221+
<Run Text="{x:Bind SavedActionName, Mode=OneWay}" />
222+
</TextBlock>
223+
<TextBlock x:Name="ActionSavedKeyChordText"
224+
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionKeyChord), Mode=OneWay}">
225+
<Run Text="Key Chord: " />
226+
<Run Text="{x:Bind SavedActionKeyChord, Mode=OneWay}" />
227+
</TextBlock>
228+
<TextBlock x:Name="ActionSavedCommandLineText"
229+
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionCommandLine), Mode=OneWay}">
230+
<Run Text="Input: " />
231+
<Run Text="{x:Bind SavedActionCommandLine, Mode=OneWay}" />
232+
</TextBlock>
233+
</StackPanel>
234+
</mux:TeachingTip.Content>
235+
</mux:TeachingTip>
236+
<mux:TeachingTip x:Name="ActionSaveFailedToast"
237+
x:Uid="ActionSaveFailedToast"
238+
Title="Action Save Failed"
239+
x:Load="False"
240+
IsLightDismissEnabled="True">
241+
<mux:TeachingTip.Content>
242+
<TextBlock x:Name="ActionSaveFailedMessage"
243+
Text="" />
244+
</mux:TeachingTip.Content>
245+
</mux:TeachingTip>
207246
</Grid>
208247
</Page>

0 commit comments

Comments
 (0)