mirror of
https://github.com/microsoft/terminal.git
synced 2026-07-08 17:56:28 +00:00
add a subcommand for creating a new profile from the CWD
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
#include "../TerminalSettingsAppAdapterLib/TerminalSettings.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Text;
|
||||
@@ -1335,6 +1337,80 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleNewProfile(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
if (!args)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& realArgs = args.ActionArgs().try_as<NewProfileArgs>();
|
||||
if (!realArgs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto newProfile = _settings.CreateNewProfile();
|
||||
if (!newProfile)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!realArgs.Name().empty())
|
||||
{
|
||||
newProfile.Name(realArgs.Name());
|
||||
}
|
||||
|
||||
if (!realArgs.StartingDirectory().empty())
|
||||
{
|
||||
newProfile.StartingDirectory(realArgs.StartingDirectory());
|
||||
}
|
||||
|
||||
if (!realArgs.Commandline().empty())
|
||||
{
|
||||
newProfile.Commandline(realArgs.Commandline());
|
||||
}
|
||||
|
||||
// Resolve color scheme: either a specific one, or pick a random one.
|
||||
winrt::hstring schemeName;
|
||||
if (realArgs.RandomColorScheme())
|
||||
{
|
||||
const auto schemes = _settings.GlobalSettings().ColorSchemes();
|
||||
const auto schemeCount = schemes.Size();
|
||||
if (schemeCount > 0)
|
||||
{
|
||||
std::vector<winrt::hstring> names;
|
||||
names.reserve(schemeCount);
|
||||
for (const auto& kvp : schemes)
|
||||
{
|
||||
names.emplace_back(kvp.Key());
|
||||
}
|
||||
std::random_device rd;
|
||||
std::mt19937 gen{ rd() };
|
||||
std::uniform_int_distribution<size_t> dist{ 0, names.size() - 1 };
|
||||
schemeName = names[dist(gen)];
|
||||
}
|
||||
}
|
||||
else if (!realArgs.ColorScheme().empty())
|
||||
{
|
||||
schemeName = realArgs.ColorScheme();
|
||||
}
|
||||
|
||||
if (!schemeName.empty())
|
||||
{
|
||||
newProfile.DefaultAppearance().DarkColorSchemeName(schemeName);
|
||||
newProfile.DefaultAppearance().LightColorSchemeName(schemeName);
|
||||
}
|
||||
|
||||
_settings.WriteSettingsToDisk();
|
||||
args.Handled(true);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
void TerminalPage::ActionSaved(winrt::hstring input, winrt::hstring name, winrt::hstring keyChord)
|
||||
{
|
||||
// If we haven't ever loaded the TeachingTip, then do so now and
|
||||
|
||||
@@ -209,6 +209,7 @@ void AppCommandlineArgs::_buildParser()
|
||||
_buildSwapPaneParser();
|
||||
_buildFocusPaneParser();
|
||||
_buildSaveSnippetParser();
|
||||
_buildNewProfileParser();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -603,6 +604,115 @@ void AppCommandlineArgs::_buildSaveSnippetParser()
|
||||
setupSubcommand(_saveCommand);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Adds the `new-profile` subcommand and related options to the commandline parser.
|
||||
// This subcommand creates a new profile in the user's settings file, using the
|
||||
// current working directory as the starting directory by default. It does not
|
||||
// open a new tab or window.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AppCommandlineArgs::_buildNewProfileParser()
|
||||
{
|
||||
_newProfileCommand = _app.add_subcommand("new-profile", RS_A(L"CmdNewProfileDesc"));
|
||||
|
||||
auto setupSubcommand = [this](auto* subcommand) {
|
||||
subcommand->add_option("--name,-n", _newProfileName, RS_A(L"CmdNewProfileNameArgDesc"));
|
||||
subcommand->add_option("--startingDirectory,-d", _newProfileStartingDirectory, RS_A(L"CmdNewProfileStartingDirArgDesc"));
|
||||
subcommand->add_option("--colorScheme,-c", _newProfileColorScheme, RS_A(L"CmdNewProfileColorSchemeArgDesc"));
|
||||
subcommand->add_flag("--random,-r", _newProfileRandomColorScheme, RS_A(L"CmdNewProfileRandomColorSchemeArgDesc"));
|
||||
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]() {
|
||||
ActionAndArgs newProfileAction{};
|
||||
newProfileAction.Action(ShortcutAction::NewProfile);
|
||||
NewProfileArgs args{};
|
||||
|
||||
// Capture the current working directory now, while we're still
|
||||
// running in the spawned wt.exe process. The action will be
|
||||
// delegated to the existing monarch wt.exe process, which will
|
||||
// have a different CWD.
|
||||
std::wstring cwd;
|
||||
try
|
||||
{
|
||||
cwd = wil::GetCurrentDirectoryW<std::wstring>();
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
// Use the --startingDirectory if provided, else use the CWD we captured.
|
||||
std::wstring startingDir;
|
||||
if (!_newProfileStartingDirectory.empty())
|
||||
{
|
||||
startingDir = winrt::to_hstring(_newProfileStartingDirectory).c_str();
|
||||
}
|
||||
else
|
||||
{
|
||||
startingDir = cwd;
|
||||
}
|
||||
args.StartingDirectory(winrt::hstring{ startingDir });
|
||||
|
||||
// Use the --name if provided, else use the leaf of the starting directory.
|
||||
std::wstring name;
|
||||
if (!_newProfileName.empty())
|
||||
{
|
||||
name = winrt::to_hstring(_newProfileName).c_str();
|
||||
}
|
||||
else if (!startingDir.empty())
|
||||
{
|
||||
try
|
||||
{
|
||||
name = std::filesystem::path{ startingDir }.filename().wstring();
|
||||
if (name.empty())
|
||||
{
|
||||
// path ended in a separator (e.g. "C:\") - try the parent
|
||||
name = std::filesystem::path{ startingDir }.parent_path().filename().wstring();
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
args.Name(winrt::hstring{ name });
|
||||
|
||||
if (!_newProfileColorScheme.empty())
|
||||
{
|
||||
args.ColorScheme(winrt::to_hstring(_newProfileColorScheme));
|
||||
}
|
||||
args.RandomColorScheme(_newProfileRandomColorScheme);
|
||||
|
||||
if (!_commandline.empty())
|
||||
{
|
||||
std::ostringstream cmdlineBuffer;
|
||||
for (const auto& arg : _commandline)
|
||||
{
|
||||
if (cmdlineBuffer.tellp() != 0)
|
||||
{
|
||||
cmdlineBuffer << ' ';
|
||||
}
|
||||
if (arg.find(" ") != std::string::npos)
|
||||
{
|
||||
cmdlineBuffer << '"' << arg << '"';
|
||||
}
|
||||
else
|
||||
{
|
||||
cmdlineBuffer << arg;
|
||||
}
|
||||
}
|
||||
args.Commandline(winrt::to_hstring(cmdlineBuffer.str()));
|
||||
}
|
||||
|
||||
newProfileAction.Args(args);
|
||||
_startupActions.push_back(newProfileAction);
|
||||
});
|
||||
};
|
||||
|
||||
setupSubcommand(_newProfileCommand);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Add the `NewTerminalArgs` parameters to the given subcommand. This enables
|
||||
// that subcommand to support all the properties in a NewTerminalArgs.
|
||||
@@ -777,7 +887,8 @@ bool AppCommandlineArgs::_noCommandsProvided()
|
||||
*_focusPaneShort ||
|
||||
*_newPaneShort.subcommand ||
|
||||
*_newPaneCommand.subcommand ||
|
||||
*_saveCommand);
|
||||
*_saveCommand ||
|
||||
*_newProfileCommand);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -815,6 +926,11 @@ void AppCommandlineArgs::_resetStateToDefault()
|
||||
_focusPaneTarget = -1;
|
||||
_loadPersistedLayoutIdx = -1;
|
||||
|
||||
_newProfileName.clear();
|
||||
_newProfileStartingDirectory.clear();
|
||||
_newProfileColorScheme.clear();
|
||||
_newProfileRandomColorScheme = false;
|
||||
|
||||
// DON'T clear _launchMode here! This will get called once for every
|
||||
// subcommand, so we don't want `wt -F new-tab ; split-pane` clearing out
|
||||
// the "global" fullscreen flag (-F).
|
||||
@@ -1002,21 +1118,23 @@ bool AppCommandlineArgs::ShouldExitEarly() const noexcept
|
||||
// - <none>
|
||||
void AppCommandlineArgs::ValidateStartupCommands()
|
||||
{
|
||||
// If we only have a single x-save command, then set our target to the
|
||||
// current terminal window. This will prevent us from spawning a new
|
||||
// window just to save the commandline.
|
||||
// If we only have a single x-save or new-profile command, then set our
|
||||
// target to the current terminal window. This will prevent us from spawning
|
||||
// a new window just to save the commandline or create a profile.
|
||||
if (_startupActions.size() == 1 &&
|
||||
_startupActions.front().Action() == ShortcutAction::SaveSnippet &&
|
||||
(_startupActions.front().Action() == ShortcutAction::SaveSnippet ||
|
||||
_startupActions.front().Action() == ShortcutAction::NewProfile) &&
|
||||
_windowTarget.empty())
|
||||
{
|
||||
_windowTarget = "0";
|
||||
}
|
||||
// If we parsed no commands, or the first command we've parsed is not a new
|
||||
// tab action, prepend a new-tab command to the front of the list.
|
||||
// (also, we don't need to do this if the only action is a x-save)
|
||||
// (also, we don't need to do this if the only action is a x-save or new-profile)
|
||||
else if (_startupActions.empty() ||
|
||||
(_startupActions.front().Action() != ShortcutAction::NewTab &&
|
||||
_startupActions.front().Action() != ShortcutAction::SaveSnippet))
|
||||
_startupActions.front().Action() != ShortcutAction::SaveSnippet &&
|
||||
_startupActions.front().Action() != ShortcutAction::NewProfile))
|
||||
{
|
||||
// Build the NewTab action from the values we've parsed on the commandline.
|
||||
NewTerminalArgs newTerminalArgs{};
|
||||
|
||||
@@ -93,6 +93,7 @@ private:
|
||||
CLI::App* _focusPaneCommand;
|
||||
CLI::App* _focusPaneShort;
|
||||
CLI::App* _saveCommand;
|
||||
CLI::App* _newProfileCommand;
|
||||
|
||||
// Are you adding a new sub-command? Make sure to update _noCommandsProvided!
|
||||
|
||||
@@ -125,6 +126,11 @@ private:
|
||||
int _focusPaneTarget{ -1 };
|
||||
std::string _saveInputName;
|
||||
std::string _keyChordOption;
|
||||
|
||||
std::string _newProfileName;
|
||||
std::string _newProfileStartingDirectory;
|
||||
std::string _newProfileColorScheme;
|
||||
bool _newProfileRandomColorScheme{ false };
|
||||
// Are you adding more args here? Make sure to reset them in _resetStateToDefault
|
||||
|
||||
const Commandline* _currentCommandline{ nullptr };
|
||||
@@ -150,6 +156,7 @@ private:
|
||||
void _buildMovePaneParser();
|
||||
void _buildSwapPaneParser();
|
||||
void _buildFocusPaneParser();
|
||||
void _buildNewProfileParser();
|
||||
bool _noCommandsProvided();
|
||||
void _resetStateToDefault();
|
||||
int _handleExit(const CLI::App& command, const CLI::Error& e);
|
||||
|
||||
@@ -296,6 +296,21 @@
|
||||
<data name="SaveSnippetDesc" xml:space="preserve">
|
||||
<value>Save command line as input action</value>
|
||||
</data>
|
||||
<data name="CmdNewProfileDesc" xml:space="preserve">
|
||||
<value>Create a new profile from the current working directory and persist it to settings without opening a new tab or window</value>
|
||||
</data>
|
||||
<data name="CmdNewProfileNameArgDesc" xml:space="preserve">
|
||||
<value>The name of the new profile. Defaults to the leaf of the current working directory</value>
|
||||
</data>
|
||||
<data name="CmdNewProfileStartingDirArgDesc" xml:space="preserve">
|
||||
<value>The starting directory of the new profile. Defaults to the current working directory</value>
|
||||
</data>
|
||||
<data name="CmdNewProfileColorSchemeArgDesc" xml:space="preserve">
|
||||
<value>The name of the color scheme to assign to the new profile</value>
|
||||
</data>
|
||||
<data name="CmdNewProfileRandomColorSchemeArgDesc" xml:space="preserve">
|
||||
<value>Assign a random color scheme to the new profile</value>
|
||||
</data>
|
||||
<data name="SaveSnippetArgDesc" xml:space="preserve">
|
||||
<value>An optional argument</value>
|
||||
</data>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "AddMarkArgs.g.cpp"
|
||||
#include "FindMatchArgs.g.cpp"
|
||||
#include "SaveSnippetArgs.g.cpp"
|
||||
#include "NewProfileArgs.g.cpp"
|
||||
#include "ToggleCommandPaletteArgs.g.cpp"
|
||||
#include "SuggestionsArgs.g.cpp"
|
||||
#include "NewWindowArgs.g.cpp"
|
||||
@@ -924,6 +925,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
return {};
|
||||
}
|
||||
|
||||
winrt::hstring NewProfileArgs::GenerateName(const winrt::WARC::ResourceContext& context) const
|
||||
{
|
||||
auto str = std::wstring{ RS_switchable_(L"NewProfileNamePrefix") };
|
||||
if (!Name().empty())
|
||||
{
|
||||
fmt::format_to(std::back_inserter(str), FMT_COMPILE(L", name: {}"), Name());
|
||||
}
|
||||
if (!Commandline().empty())
|
||||
{
|
||||
fmt::format_to(std::back_inserter(str), FMT_COMPILE(L", commandline: {}"), Commandline());
|
||||
}
|
||||
return winrt::hstring{ str };
|
||||
}
|
||||
|
||||
static winrt::hstring _FormatColorString(const Control::SelectionColor& selectionColor)
|
||||
{
|
||||
if (!selectionColor)
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "AddMarkArgs.g.h"
|
||||
#include "MoveTabArgs.g.h"
|
||||
#include "SaveSnippetArgs.g.h"
|
||||
#include "NewProfileArgs.g.h"
|
||||
#include "ToggleCommandPaletteArgs.g.h"
|
||||
#include "SuggestionsArgs.g.h"
|
||||
#include "FindMatchArgs.g.h"
|
||||
@@ -225,6 +226,14 @@ protected: \
|
||||
X(winrt::hstring, Commandline, "commandline", args->Commandline().empty(), ArgTypeHint::None, L"") \
|
||||
X(winrt::hstring, KeyChord, "keyChord", false, ArgTypeHint::None, L"")
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define NEW_PROFILE_ARGS(X) \
|
||||
X(winrt::hstring, Name, "name", false, ArgTypeHint::None, L"") \
|
||||
X(winrt::hstring, StartingDirectory, "startingDirectory", false, ArgTypeHint::None, L"") \
|
||||
X(winrt::hstring, ColorScheme, "colorScheme", false, ArgTypeHint::None, L"") \
|
||||
X(winrt::hstring, Commandline, "commandline", false, ArgTypeHint::None, L"") \
|
||||
X(bool, RandomColorScheme, "randomColorScheme", false, ArgTypeHint::None, false)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#define SUGGESTIONS_ARGS(X) \
|
||||
X(SuggestionsSource, Source, "source", false, ArgTypeHint::None, SuggestionsSource::Tasks) \
|
||||
@@ -930,6 +939,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
|
||||
ACTION_ARGS_STRUCT(SaveSnippetArgs, SAVE_TASK_ARGS);
|
||||
|
||||
ACTION_ARGS_STRUCT(NewProfileArgs, NEW_PROFILE_ARGS);
|
||||
|
||||
ACTION_ARGS_STRUCT(SuggestionsArgs, SUGGESTIONS_ARGS);
|
||||
|
||||
ACTION_ARGS_STRUCT(FindMatchArgs, FIND_MATCH_ARGS);
|
||||
@@ -1066,6 +1077,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
BASIC_FACTORY(MoveTabArgs);
|
||||
BASIC_FACTORY(OpenSettingsArgs);
|
||||
BASIC_FACTORY(SaveSnippetArgs);
|
||||
BASIC_FACTORY(NewProfileArgs);
|
||||
BASIC_FACTORY(FindMatchArgs);
|
||||
BASIC_FACTORY(NewWindowArgs);
|
||||
BASIC_FACTORY(FocusPaneArgs);
|
||||
|
||||
@@ -394,6 +394,17 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
String KeyChord;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass NewProfileArgs : IActionArgs, IActionArgsDescriptorAccess
|
||||
{
|
||||
NewProfileArgs();
|
||||
NewProfileArgs(String Name, String StartingDirectory, String ColorScheme, String Commandline, Boolean RandomColorScheme);
|
||||
String Name;
|
||||
String StartingDirectory;
|
||||
String ColorScheme;
|
||||
String Commandline;
|
||||
Boolean RandomColorScheme;
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass NewWindowArgs : IActionArgs, IActionArgsDescriptorAccess
|
||||
{
|
||||
NewWindowArgs(INewContentArgs contentArgs);
|
||||
|
||||
@@ -106,6 +106,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{ ShortcutAction::MultipleActions, USES_RESOURCE(L"MultipleActions") },
|
||||
{ ShortcutAction::NewTab, USES_RESOURCE(L"NewTabCommandKey") },
|
||||
{ ShortcutAction::NewWindow, USES_RESOURCE(L"NewWindowCommandKey") },
|
||||
{ ShortcutAction::NewProfile, USES_RESOURCE(L"NewProfileNamePrefix") },
|
||||
{ ShortcutAction::NextTab, USES_RESOURCE(L"NextTabCommandKey") },
|
||||
{ ShortcutAction::OpenAbout, USES_RESOURCE(L"OpenAboutCommandKey") },
|
||||
{ ShortcutAction::OpenCWD, USES_RESOURCE(L"OpenCWDCommandKey") },
|
||||
|
||||
@@ -164,7 +164,9 @@
|
||||
// They don't need to be parsed by the settings model, or saved as actions to
|
||||
// JSON.
|
||||
#define INTERNAL_SHORTCUT_ACTIONS \
|
||||
ON_ALL_ACTIONS(SaveSnippet)
|
||||
ON_ALL_ACTIONS(SaveSnippet) \
|
||||
ON_ALL_ACTIONS(NewProfile)
|
||||
|
||||
#define INTERNAL_SHORTCUT_ACTIONS_WITH_ARGS \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(SaveSnippet)\
|
||||
#define INTERNAL_SHORTCUT_ACTIONS_WITH_ARGS \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(SaveSnippet) \
|
||||
ON_ALL_ACTIONS_WITH_ARGS(NewProfile)
|
||||
|
||||
@@ -734,6 +734,9 @@
|
||||
<data name="SaveSnippetNamePrefix" xml:space="preserve">
|
||||
<value>Save Snippet</value>
|
||||
</data>
|
||||
<data name="NewProfileNamePrefix" xml:space="preserve">
|
||||
<value>New profile</value>
|
||||
</data>
|
||||
<data name="QuickFixCommandKey" xml:space="preserve">
|
||||
<value>Open quick fix menu</value>
|
||||
</data>
|
||||
|
||||
Reference in New Issue
Block a user