Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce parsed command line text to command palette #8515

Merged
8 commits merged into from
Dec 16, 2020
55 changes: 55 additions & 0 deletions src/cascadia/TerminalApp/AppCommandlineArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -720,3 +720,58 @@ int AppCommandlineArgs::ParseArgs(winrt::array_view<const winrt::hstring>& args)
// built in _appArgs, which we'll use when the application starts up.
return 0;
}

// Method Description:
// - Attempts to parse an array of commandline args into a list of
// commands to execute, and then parses these commands. As commands are
// successfully parsed, they will generate ShortcutActions for us to be
// able to execute. If we fail to parse any commands, we'll return the
// error code from the failure to parse that command, and stop processing
// additional commands.
// - The first arg in args should be the program name "wt" (or some variant). It
// will be ignored during parsing.
// Arguments:
// - args: ExecuteCommandlineArgs describing the command line to parse
// Return Value:
// - 0 if the commandline was successfully parsed
int AppCommandlineArgs::ParseArgs(const winrt::Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args)
{
if (!args || args.Commandline().empty())
{
return 0;
}

// Convert the commandline into an array of args with
// CommandLineToArgvW, similar to how the app typically does when
// called from the commandline.
int argc = 0;
wil::unique_any<LPWSTR*, decltype(&::LocalFree), ::LocalFree> argv{ CommandLineToArgvW(args.Commandline().c_str(), &argc) };
if (argv)
{
std::vector<winrt::hstring> args;

// Make sure the first argument is wt.exe, because ParseArgs will
// always skip the program name. The particular value of this first
// string doesn't terribly matter.
args.emplace_back(L"wt.exe");
for (auto& elem : wil::make_range(argv.get(), argc))
{
args.emplace_back(elem);
}
winrt::array_view<const winrt::hstring> argsView{ args };
return ParseArgs(argsView);
}
return 0;
}

// Method Description:
// - Allows disabling addition of help-related info in the exit message
// Arguments:
// - none
// Return Value:
// - none
void AppCommandlineArgs::DisableHelpInExitMessage()
{
_app.set_help_flag();
_app.set_help_all_flag();
}
3 changes: 3 additions & 0 deletions src/cascadia/TerminalApp/AppCommandlineArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class TerminalApp::AppCommandlineArgs final

std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchMode> GetLaunchMode() const noexcept;

int ParseArgs(const winrt::Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args);
void DisableHelpInExitMessage();

private:
static const std::wregex _commandDelimiterRegex;

Expand Down
35 changes: 34 additions & 1 deletion src/cascadia/TerminalApp/CommandPalette.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

#include "pch.h"
#include "CommandPalette.h"

#include "AppCommandlineArgs.h"
#include <LibraryResources.h>

#include "CommandPalette.g.cpp"
Expand Down Expand Up @@ -733,6 +733,38 @@ namespace winrt::TerminalApp::implementation
{
_noMatchesText().Visibility(Visibility::Collapsed);
}

if (_currentMode == CommandPaletteMode::CommandlineMode)
{
ParsedCommandLineText(L"");

const auto commandLine = _getTrimmedInput();
if (!commandLine.empty())
{
ExecuteCommandlineArgs args{ commandLine };
::TerminalApp::AppCommandlineArgs appArgs;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of me wonders if the CommandPalette should just keep a single AppCommandlineArgs instance as a member, and then reset it and re-use it for parsing each time.

That being said, this solution certainly does work

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zadjii-msft - I see your point. I allowed this to myself, since today we do allocate an instance upon every execution. However, now we will actually allocate an instance on every character in the search - so it makes sense to cache it. I will see if there is a good way to reset the instance - since the internal reset we have there now is partial IIRC.

appArgs.DisableHelpInExitMessage();

if (appArgs.ParseArgs(args) == 0)
{
const auto& commands = appArgs.GetStartupActions();
if (commands.size() > 0)
{
std::wstring commandDescription{ RS_(L"CommandPalette_ParsedCommandLine") + L":" };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(asking the team) Not too familiar with how localization works, but should we move the : to the resw?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DHowett would know for sure, but my guess would be yes, we need to move it to the resw. I'm thinking the resource would need to be

Executing command line will invoke the following commands:{0}

and then we fmt::format that with the list of generated names (joined with \n\t)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do agree that : should be there. My idea was not to include {0} so if we decide to put them in different Runs (e.g. make this caption red or green) we won't need new translation. WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I actually agree with that. We may want the actions to each be a separate run of text for whatever reason.

for (const auto& command : commands)
{
commandDescription += L"\n\t" + command.Args().GenerateName();
}
ParsedCommandLineText(commandDescription.data());
}
}
else
{
ParsedCommandLineText(RS_(L"CommandPalette_FailedParsingCommandLine") + L":\n\t" + til::u8u16(appArgs.GetExitMessage()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same down here, we should use fmt::format to add the error text to the resource here.

NoticeFontNotFound in the TerminalControl project is a good example

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

}
_noMatchesText().Visibility(Visibility::Visible);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious -- we're showing the no matches string all the time now? What's this look like?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DHowett - I am afraid this might be a leftover.. 🤦 which is hidden by the ParsedCommandLineText... 🤔
Fixing it immediately, after I am finalizing the live search for initial review. Hopefully today (it is already 2am for me 😄)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. It is a leftover. Thanks!

}
}
}

void CommandPalette::_evaluatePrefix()
Expand Down Expand Up @@ -837,6 +869,7 @@ namespace winrt::TerminalApp::implementation
}
}

ParsedCommandLineText(L"");
_searchBox().Text(L"");
_searchBox().Select(_searchBox().Text().size(), 0);
// Leaving this block of code outside the above if-statement
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/CommandPalette.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ namespace winrt::TerminalApp::implementation
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, PrefixCharacter, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ControlName, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ParentCommandName, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ParsedCommandLineText, _PropertyChangedHandlers);

private:
friend struct CommandPaletteT<CommandPalette>; // for Xaml to bind events
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/CommandPalette.idl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace TerminalApp
String PrefixCharacter { get; };
String ControlName { get; };
String ParentCommandName { get; };
String ParsedCommandLineText { get; };

Windows.Foundation.Collections.IObservableVector<FilteredCommand> FilteredActions { get; };

Expand Down
38 changes: 38 additions & 0 deletions src/cascadia/TerminalApp/CommandPalette.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ the MIT License. See LICENSE in the project root for license information. -->

<!-- This creates an instance of our CommandKeyChordVisibilityConverter we can reference below -->
<local:EmptyStringVisibilityConverter x:Key="CommandKeyChordVisibilityConverter"/>
<local:EmptyStringVisibilityConverter x:Key="ParsedCommandLineTextVisibilityConverter"/>
<local:EmptyStringVisibilityConverter x:Key="ParentCommandVisibilityConverter"/>
<local:HasNestedCommandsVisibilityConverter x:Key="HasNestedCommandsVisibilityConverter"/>
<local:IconPathConverter x:Key="IconSourceConverter"/>
Expand Down Expand Up @@ -67,6 +68,16 @@ the MIT License. See LICENSE in the project root for license information. -->
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>

<!-- ParsedCommandLineText styles -->
<Style x:Key="ParsedCommandLineBorderStyle" TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<Style x:Key="ParsedCommandLineTextBlockStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
Expand Down Expand Up @@ -98,6 +109,16 @@ the MIT License. See LICENSE in the project root for license information. -->
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>

<!-- ParsedCommandLineText styles -->
<Style x:Key="ParsedCommandLineBorderStyle" TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Background" Value="{ThemeResource SystemAltMediumLowColor}" />
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
<Style x:Key="ParsedCommandLineTextBlockStyle" TargetType="TextBlock">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spacing looks strange around here.. Sup?

<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
Expand All @@ -108,6 +129,9 @@ the MIT License. See LICENSE in the project root for license information. -->
<Style x:Key="KeyChordBorderStyle" TargetType="Border"/>
<Style x:Key="KeyChordTextBlockStyle" TargetType="TextBlock"/>

<!-- ParsedCommandLineText styles (use XAML defaults for High Contrast theme) -->
<Style x:Key="ParsedCommandLineBoderStyle" TargetType="Border"/>
<Style x:Key="ParsedCommandLineTextBlockStyle" TargetType="TextBlock"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
Expand Down Expand Up @@ -226,6 +250,20 @@ the MIT License. See LICENSE in the project root for license information. -->
Text="{x:Bind NoMatchesText, Mode=OneWay}">
</TextBlock>

<Border
Grid.Row="1"
Visibility="{x:Bind ParsedCommandLineText, Mode=OneWay, Converter={StaticResource ParsedCommandLineTextVisibilityConverter}}"
Style="{ThemeResource ParsedCommandLineBorderStyle}"
Padding="16"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">

<TextBlock
FontStyle="Italic"
TextWrapping="Wrap"
Text="{x:Bind ParsedCommandLineText, Mode=OneWay}"/>
</Border>

<ListView
Grid.Row="2"
x:Name="_filteredActionsView"
Expand Down
7 changes: 7 additions & 0 deletions src/cascadia/TerminalApp/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,13 @@
<data name="CommandPalette_NoMatchesText.Text" xml:space="preserve">
<value>No matching commands</value>
</data>
<data name="CommandPalette_ParsedCommandLine" xml:space="preserve">
<value>Executing command line will invoke the following commands</value>
<comment>Will be followed by a list of strings describing parsed commands</comment>
</data>
<data name="CommandPalette_FailedParsingCommandLine" xml:space="preserve">
<value>Failed parsing command line</value>
</data>
<data name="CommandPaletteControlName" xml:space="preserve">
<value>Command Palette</value>
</data>
Expand Down
29 changes: 3 additions & 26 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2644,35 +2644,12 @@ namespace winrt::TerminalApp::implementation
// - an empty list if we failed to parse, otherwise a list of actions to execute.
std::vector<ActionAndArgs> TerminalPage::ConvertExecuteCommandlineToActions(const ExecuteCommandlineArgs& args)
{
if (!args || args.Commandline().empty())
::TerminalApp::AppCommandlineArgs appArgs;
if (appArgs.ParseArgs(args) == 0)
{
return {};
return appArgs.GetStartupActions();
}
// Convert the commandline into an array of args with
// CommandLineToArgvW, similar to how the app typically does when
// called from the commandline.
int argc = 0;
wil::unique_any<LPWSTR*, decltype(&::LocalFree), ::LocalFree> argv{ CommandLineToArgvW(args.Commandline().c_str(), &argc) };
if (argv)
{
std::vector<winrt::hstring> args;

// Make sure the first argument is wt.exe, because ParseArgs will
// always skip the program name. The particular value of this first
// string doesn't terribly matter.
args.emplace_back(L"wt.exe");
for (auto& elem : wil::make_range(argv.get(), argc))
{
args.emplace_back(elem);
}
winrt::array_view<const winrt::hstring> argsView{ args };

::TerminalApp::AppCommandlineArgs appArgs;
if (appArgs.ParseArgs(argsView) == 0)
{
return appArgs.GetStartupActions();
}
}
return {};
}

Expand Down