Compare commits

...

8 Commits

Author SHA1 Message Date
Mike Griese
76a9c7cd6b mostly stashing so I make sure I don't lose this 2024-05-28 09:37:36 -05:00
Mike Griese
e0b047762f format 2024-03-19 05:30:20 -05:00
Mike Griese
03f57acd3e Merge branch 'main' into save_input_action 2024-03-18 13:55:16 -07:00
Eric Nelson
e54b4a4143 Clean up of Save Action ActionArgs 2024-01-19 20:04:55 -08:00
Eric Nelson
eec4f6bf4a Saved Input: Use TextBlocks instead of TextBoxes for toast, exclude
blank arges from generated name
2024-01-09 18:20:03 -08:00
Eric Nelson
8bec5ec4a0 Handle key chord parse failure 2024-01-02 04:22:16 -08:00
Eric Nelson
dd46338942 Added command line options for Save Input 2024-01-01 15:51:45 -08:00
Eric Nelson
83e3b8ca60 Add ability to save input action from command line
- Save to disk
- If command line is empty use selection
- Show toast
2024-01-01 10:22:59 -08:00
20 changed files with 306 additions and 7 deletions

View File

@@ -1232,6 +1232,51 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::_HandleSaveTask(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (args)
{
if (const auto& realArgs = args.ActionArgs().try_as<SaveTaskArgs>())
{
if (realArgs.Commandline().empty())
{
if (const auto termControl{ _GetActiveControl() })
{
if (termControl.HasSelection())
{
const auto selections{ termControl.SelectedText(true) };
auto selection = std::accumulate(selections.begin(), selections.end(), std::wstring());
realArgs.Commandline(selection);
}
}
}
try
{
winrt::Microsoft::Terminal::Control::KeyChord keyChord = nullptr;
if (!realArgs.KeyChord().empty())
{
keyChord = KeyChordSerialization::FromString(winrt::to_hstring(realArgs.KeyChord()));
}
_settings.GlobalSettings().ActionMap().AddSendInputAction(realArgs.Name(), realArgs.Commandline(), keyChord);
_settings.WriteSettingsToDisk();
ActionSaved(realArgs.Commandline(), realArgs.Name(), realArgs.KeyChord());
}
catch (const winrt::hresult_error& ex)
{
auto code = ex.code();
auto message = ex.message();
ActionSaveFailed(message);
args.Handled(true);
return;
}
args.Handled(true);
}
}
}
void TerminalPage::_HandleSelectCommand(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{

View File

@@ -209,6 +209,7 @@ void AppCommandlineArgs::_buildParser()
_buildMovePaneParser();
_buildSwapPaneParser();
_buildFocusPaneParser();
_buildSaveParser();
}
// Method Description:
@@ -537,6 +538,72 @@ void AppCommandlineArgs::_buildFocusPaneParser()
setupSubcommand(_focusPaneShort);
}
void AppCommandlineArgs::_buildSaveParser()
{
_saveCommand = _app.add_subcommand("save", RS_A(L"SaveActionDesc"));
auto setupSubcommand = [this](auto* subcommand) {
subcommand->add_option("--name,-n", _saveInputName, RS_A(L"SaveActionArgDesc"));
subcommand->add_option("--keychord,-k", _keyChordOption, RS_A(L"KeyChordArgDesc"));
subcommand->add_option("command,", _commandline, RS_A(L"CmdCommandArgDesc"));
subcommand->positionals_at_end(true);
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
// that `this` will still be safe - this function just lets us know this
// command was parsed.
subcommand->callback([&, this]() {
// Build the NewTab action from the values we've parsed on the commandline.
ActionAndArgs saveAction{};
saveAction.Action(ShortcutAction::SaveTask);
// _getNewTerminalArgs MUST be called before parsing any other options,
// as it might clear those options while finding the commandline
SaveTaskArgs args{};
if (!_commandline.empty())
{
std::ostringstream cmdlineBuffer;
for (const auto& arg : _commandline)
{
if (cmdlineBuffer.tellp() != 0)
{
// If there's already something in here, prepend a space
cmdlineBuffer << ' ';
}
if (arg.find(" ") != std::string::npos)
{
cmdlineBuffer << '"' << arg << '"';
}
else
{
cmdlineBuffer << arg;
}
}
args.Commandline(winrt::to_hstring(cmdlineBuffer.str()));
}
if (!_keyChordOption.empty())
{
args.KeyChord(winrt::to_hstring(_keyChordOption));
}
if (!_saveInputName.empty())
{
winrt::hstring hString = winrt::to_hstring(_saveInputName);
args.Name(hString);
}
saveAction.Args(args);
_startupActions.push_back(saveAction);
});
};
setupSubcommand(_saveCommand);
}
// Method Description:
// - Add the `NewTerminalArgs` parameters to the given subcommand. This enables
// that subcommand to support all the properties in a NewTerminalArgs.
@@ -700,7 +767,8 @@ bool AppCommandlineArgs::_noCommandsProvided()
*_focusPaneCommand ||
*_focusPaneShort ||
*_newPaneShort.subcommand ||
*_newPaneCommand.subcommand);
*_newPaneCommand.subcommand ||
*_saveCommand);
}
// Method Description:

View File

@@ -92,6 +92,7 @@ private:
CLI::App* _swapPaneCommand;
CLI::App* _focusPaneCommand;
CLI::App* _focusPaneShort;
CLI::App* _saveCommand;
// Are you adding a new sub-command? Make sure to update _noCommandsProvided!
@@ -121,6 +122,8 @@ private:
bool _focusPrevTab{ false };
int _focusPaneTarget{ -1 };
std::string _saveInputName;
std::string _keyChordOption;
// Are you adding more args here? Make sure to reset them in _resetStateToDefault
const Commandline* _currentCommandline{ nullptr };
@@ -139,6 +142,7 @@ private:
winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs _getNewTerminalArgs(NewTerminalSubcommand& subcommand);
void _addNewTerminalArgs(NewTerminalSubcommand& subcommand);
void _buildParser();
void _buildSaveParser();
void _buildNewTabParser();
void _buildSplitPaneParser();
void _buildFocusTabParser();

View File

@@ -291,6 +291,15 @@
<data name="CmdCommandArgDesc" xml:space="preserve">
<value>An optional command, with arguments, to be spawned in the new tab or pane</value>
</data>
<data name="SaveActionDesc" xml:space="preserve">
<value>Save command line as input action</value>
</data>
<data name="SaveActionArgDesc" xml:space="preserve">
<value>An optional argument</value>
</data>
<data name="KeyChordArgDesc" xml:space="preserve">
<value>An optional argument</value>
</data>
<data name="CmdFocusTabDesc" xml:space="preserve">
<value>Move focus to another tab</value>
</data>
@@ -905,4 +914,10 @@
<data name="RestartConnectionToolTip" xml:space="preserve">
<value>Restart the active pane connection</value>
</data>
<data name="ActionSavedToast.Title" xml:space="preserve">
<value>Action saved</value>
</data>
<data name="ActionSaveFailedToast.Title" xml:space="preserve">
<value>Action save failed</value>
</data>
</root>

View File

@@ -337,7 +337,9 @@
</ItemGroup>
<!-- ========================= Misc Files ======================== -->
<ItemGroup>
<PRIResource Include="Resources\en-US\Resources.resw" />
<PRIResource Include="Resources\en-US\Resources.resw">
<SubType>Designer</SubType>
</PRIResource>
<PRIResource Include="Resources\en-US\ContextMenu.resw" />
<OCResourceDirectory Include="Resources" />
</ItemGroup>
@@ -446,10 +448,8 @@
</ItemDefinitionGroup>
<!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<!-- 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. -->
<Import Project="$(OpenConsoleDir)src\common.nugetversions.targets" />
<!--
By default, the PRI file will contain resource paths beginning with the
project name. Since we enabled XBF embedding, this *also* includes App.xbf.
@@ -470,4 +470,4 @@
</ItemGroup>
</Target>
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
</Project>
</Project>

View File

@@ -4226,6 +4226,65 @@ namespace winrt::TerminalApp::implementation
}
}
winrt::fire_and_forget TerminalPage::ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord)
{
auto weakThis{ get_weak() };
co_await wil::resume_foreground(Dispatcher());
if (auto page{ weakThis.get() })
{
// If we haven't ever loaded the TeachingTip, then do so now and
// create the toast for it.
if (page->_actionSavedToast == nullptr)
{
if (auto tip{ page->FindName(L"ActionSavedToast").try_as<MUX::Controls::TeachingTip>() })
{
page->_actionSavedToast = std::make_shared<Toast>(tip);
// Make sure to use the weak ref when setting up this
// callback.
tip.Closed({ page->get_weak(), &TerminalPage::_FocusActiveControl });
}
}
_UpdateTeachingTipTheme(ActionSavedToast().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
SavedActionName(L"Name: " + name);
SavedActionKeyChord(L"Key Chord: " + keyChord);
SavedActionCommandLine(L"Input: " + input);
if (page->_actionSavedToast != nullptr)
{
page->_actionSavedToast->Open();
}
}
}
winrt::fire_and_forget TerminalPage::ActionSaveFailed(winrt::hstring message)
{
auto weakThis{ get_weak() };
co_await wil::resume_foreground(Dispatcher());
if (auto page{ weakThis.get() })
{
// If we haven't ever loaded the TeachingTip, then do so now and
// create the toast for it.
if (page->_actionSaveFailedToast == nullptr)
{
if (auto tip{ page->FindName(L"ActionSaveFailedToast").try_as<MUX::Controls::TeachingTip>() })
{
page->_actionSaveFailedToast = std::make_shared<Toast>(tip);
// Make sure to use the weak ref when setting up this
// callback.
tip.Closed({ page->get_weak(), &TerminalPage::_FocusActiveControl });
}
}
_UpdateTeachingTipTheme(ActionSaveFailedToast().try_as<winrt::Windows::UI::Xaml::FrameworkElement>());
ActionSaveFailedMessage().Text(message);
if (page->_actionSaveFailedToast != nullptr)
{
page->_actionSaveFailedToast->Open();
}
}
}
// Method Description:
// - Called when an attempt to rename the window has failed. This will open
// the toast displaying a message to the user that the attempt to rename

View File

@@ -149,6 +149,8 @@ namespace winrt::TerminalApp::implementation
winrt::hstring KeyboardServiceDisabledText();
winrt::fire_and_forget IdentifyWindow();
winrt::fire_and_forget ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord);
winrt::fire_and_forget ActionSaveFailed(winrt::hstring message);
winrt::fire_and_forget RenameFailed();
winrt::fire_and_forget ShowTerminalWorkingDirectory();
@@ -200,6 +202,10 @@ namespace winrt::TerminalApp::implementation
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, _PropertyChangedHandlers, nullptr);
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, FrameBrush, _PropertyChangedHandlers, nullptr);
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionName, _PropertyChangedHandlers, L"");
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionKeyChord, _PropertyChangedHandlers, L"");
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, SavedActionCommandLine, _PropertyChangedHandlers, L"");
private:
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
std::optional<HWND> _hostingHwnd;
@@ -260,6 +266,8 @@ namespace winrt::TerminalApp::implementation
bool _isEmbeddingInboundListener{ false };
std::shared_ptr<Toast> _windowIdToast{ nullptr };
std::shared_ptr<Toast> _actionSavedToast{ nullptr };
std::shared_ptr<Toast> _actionSaveFailedToast{ nullptr };
std::shared_ptr<Toast> _windowRenameFailedToast{ nullptr };
std::shared_ptr<Toast> _windowCwdToast{ nullptr };

View File

@@ -72,6 +72,10 @@ namespace TerminalApp
void IdentifyWindow();
void RenameFailed();
String SavedActionName { get; };
String SavedActionKeyChord { get; };
String SavedActionCommandLine { get; };
// We cannot use the default XAML APIs because we want to make sure
// that there's only one application-global dialog visible at a time,
// and because of GH#5224.

View File

@@ -8,6 +8,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TerminalApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mtu="using:Microsoft.Terminal.UI"
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
Background="Transparent"
mc:Ignorable="d">
@@ -219,5 +220,37 @@
Title="{x:Bind WindowProperties.VirtualWorkingDirectory, Mode=OneWay}"
x:Load="False"
IsLightDismissEnabled="True" />
<mux:TeachingTip x:Name="ActionSavedToast"
x:Uid="ActionSavedToast"
Title="Action Saved"
HorizontalAlignment="Stretch"
x:Load="False"
IsLightDismissEnabled="True">
<mux:TeachingTip.Content>
<StackPanel HorizontalAlignment="Stretch"
Orientation="Vertical">
<TextBlock x:Name="ActionSavedNameText"
Text="{x:Bind SavedActionName, Mode=OneWay}"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionName), Mode=OneWay}" />
<TextBlock x:Name="ActionSavedKeyChordText"
Text="{x:Bind SavedActionKeyChord, Mode=OneWay}"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionKeyChord), Mode=OneWay}" />
<TextBlock x:Name="ActionSavedCommandLineText"
Text="{x:Bind SavedActionCommandLine, Mode=OneWay}"
Visibility="{x:Bind mtu:Converters.StringNotEmptyToVisibility(SavedActionCommandLine), Mode=OneWay}" />
</StackPanel>
</mux:TeachingTip.Content>
</mux:TeachingTip>
<mux:TeachingTip x:Name="ActionSaveFailedToast"
x:Uid="ActionSaveFailedToast"
Title="Action Save Failed"
x:Load="False"
IsLightDismissEnabled="True">
<mux:TeachingTip.Content>
<TextBlock x:Name="ActionSaveFailedMessage"
Text="" />
</mux:TeachingTip.Content>
</mux:TeachingTip>
</Grid>
</Page>

View File

@@ -1,4 +1,4 @@
<UserControl x:Class="Microsoft.Terminal.Control.SearchBoxControl"
<UserControl x:Class="Microsoft.Terminal.Control.SearchBoxControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

View File

@@ -333,7 +333,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// (The window has a min. size that ensures that there's always a scrollbar thumb.)
if (drawableRange < 0)
{
assert(false);
return;
}

View File

@@ -52,6 +52,7 @@ static constexpr std::string_view SwitchToTabKey{ "switchToTab" };
static constexpr std::string_view TabSearchKey{ "tabSearch" };
static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" };
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
static constexpr std::string_view SaveTaskKey{ "saveTask" };
static constexpr std::string_view SuggestionsKey{ "showSuggestions" };
static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" };
static constexpr std::string_view SetFocusModeKey{ "setFocusMode" };
@@ -388,6 +389,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ ShortcutAction::TabSearch, RS_(L"TabSearchCommandKey") },
{ ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") },
{ ShortcutAction::ToggleCommandPalette, MustGenerate },
{ ShortcutAction::SaveTask, MustGenerate },
{ ShortcutAction::Suggestions, MustGenerate },
{ ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") },
{ ShortcutAction::SetFocusMode, MustGenerate },

View File

@@ -32,6 +32,7 @@
#include "ScrollToMarkArgs.g.cpp"
#include "AddMarkArgs.g.cpp"
#include "FindMatchArgs.g.cpp"
#include "SaveTaskArgs.g.cpp"
#include "ToggleCommandPaletteArgs.g.cpp"
#include "SuggestionsArgs.g.cpp"
#include "NewWindowArgs.g.cpp"
@@ -939,6 +940,25 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
}
}
winrt::hstring SaveTaskArgs::GenerateName() const
{
std::wstringstream ss;
ss << RS_(L"SaveActionNamePrefix").c_str() << L" commandline: " << Commandline().c_str();
if (!Name().empty())
{
ss << L", name: " << Name().c_str();
}
if (!KeyChord().empty())
{
ss << L", keyChord " << KeyChord().c_str();
}
return winrt::hstring{ ss.str() };
}
static winrt::hstring _FormatColorString(const Control::SelectionColor& selectionColor)
{
if (!selectionColor)

View File

@@ -33,6 +33,7 @@
#include "ScrollToMarkArgs.g.h"
#include "AddMarkArgs.g.h"
#include "MoveTabArgs.g.h"
#include "SaveTaskArgs.g.h"
#include "ToggleCommandPaletteArgs.g.h"
#include "SuggestionsArgs.g.h"
#include "FindMatchArgs.g.h"
@@ -214,6 +215,12 @@ private: \
#define TOGGLE_COMMAND_PALETTE_ARGS(X) \
X(CommandPaletteLaunchMode, LaunchMode, "launchMode", false, CommandPaletteLaunchMode::Action)
////////////////////////////////////////////////////////////////////////////////
#define SAVE_TASK_ARGS(X) \
X(winrt::hstring, Name, "name", false, L"") \
X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), L"") \
X(winrt::hstring, KeyChord, "keyChord", false, L"")
////////////////////////////////////////////////////////////////////////////////
#define SUGGESTIONS_ARGS(X) \
X(SuggestionsSource, Source, "source", false, SuggestionsSource::Tasks) \
@@ -725,6 +732,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
ACTION_ARGS_STRUCT(ToggleCommandPaletteArgs, TOGGLE_COMMAND_PALETTE_ARGS);
ACTION_ARGS_STRUCT(SaveTaskArgs, SAVE_TASK_ARGS);
ACTION_ARGS_STRUCT(SuggestionsArgs, SUGGESTIONS_ARGS);
ACTION_ARGS_STRUCT(FindMatchArgs, FIND_MATCH_ARGS);
@@ -845,6 +854,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
BASIC_FACTORY(CloseTabArgs);
BASIC_FACTORY(MoveTabArgs);
BASIC_FACTORY(OpenSettingsArgs);
BASIC_FACTORY(SaveTaskArgs);
BASIC_FACTORY(FindMatchArgs);
BASIC_FACTORY(NewWindowArgs);
BASIC_FACTORY(FocusPaneArgs);

View File

@@ -344,6 +344,15 @@ namespace Microsoft.Terminal.Settings.Model
FindMatchDirection Direction { get; };
};
[default_interface] runtimeclass SaveTaskArgs : IActionArgs
{
SaveTaskArgs();
SaveTaskArgs(String Name, String Commandline, String KeyChord);
String Name;
String Commandline;
String KeyChord;
};
[default_interface] runtimeclass NewWindowArgs : IActionArgs
{
NewWindowArgs(NewTerminalArgs terminalArgs);

View File

@@ -857,6 +857,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
AddAction(*cmd);
}
void ActionMap::AddSendInputAction(winrt::hstring name, winrt::hstring input, const Control::KeyChord keys)
{
auto newAction = winrt::make<ActionAndArgs>();
newAction.Action(ShortcutAction::SendInput);
auto sendInputArgs = winrt::make<SendInputArgs>(input);
newAction.Args(sendInputArgs);
auto cmd{ make_self<Command>() };
cmd->ActionAndArgs(newAction);
if (!name.empty())
{
cmd->Name(name);
}
cmd->RegisterKey(keys);
AddAction(*cmd);
}
void ActionMap::_recursiveUpdateCommandKeybindingLabels()
{
const auto& commands{ _ExpandedCommandsCache };

View File

@@ -74,6 +74,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys);
void DeleteKeyBinding(const Control::KeyChord& keys);
void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action);
void AddSendInputAction(winrt::hstring name, winrt::hstring input, const Control::KeyChord keys);
Windows::Foundation::Collections::IVector<Model::Command> ExpandedCommands();
void ExpandCommands(const Windows::Foundation::Collections::IVectorView<Model::Profile>& profiles,

View File

@@ -32,5 +32,6 @@ namespace Microsoft.Terminal.Settings.Model
void DeleteKeyBinding(Microsoft.Terminal.Control.KeyChord keys);
void RegisterKeyBinding(Microsoft.Terminal.Control.KeyChord keys, ActionAndArgs action);
void AddSendInputAction(String name, String input, Microsoft.Terminal.Control.KeyChord keys);
}
}

View File

@@ -75,6 +75,7 @@
ON_ALL_ACTIONS(CloseTabsAfter) \
ON_ALL_ACTIONS(TabSearch) \
ON_ALL_ACTIONS(MoveTab) \
ON_ALL_ACTIONS(SaveTask) \
ON_ALL_ACTIONS(BreakIntoDebugger) \
ON_ALL_ACTIONS(TogglePaneReadOnly) \
ON_ALL_ACTIONS(EnablePaneReadOnly) \
@@ -147,6 +148,7 @@
ON_ALL_ACTIONS_WITH_ARGS(SplitPane) \
ON_ALL_ACTIONS_WITH_ARGS(SwitchToTab) \
ON_ALL_ACTIONS_WITH_ARGS(ToggleCommandPalette) \
ON_ALL_ACTIONS_WITH_ARGS(SaveTask) \
ON_ALL_ACTIONS_WITH_ARGS(FocusPane) \
ON_ALL_ACTIONS_WITH_ARGS(ExportBuffer) \
ON_ALL_ACTIONS_WITH_ARGS(ClearBuffer) \

View File

@@ -724,4 +724,7 @@
<value>Open about dialog</value>
<comment>This will open the "about" dialog, to display version info and other documentation</comment>
</data>
<data name="SaveActionNamePrefix" xml:space="preserve">
<value>Save Task</value>
</data>
</root>