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

[PowerToys Run] Add Suport for Commandline arguments in Program Plugin #5791

Merged
merged 7 commits into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.Plugin.Program.ProgramArgumentParser;
using NUnit.Framework;
using Wox.Plugin;

namespace Microsoft.Plugin.Program.UnitTests.ProgramArgumentParser
{
[TestFixture]
public class ProgramArgumentParserTests
{
[TestCase("Microsoft Edge", "Microsoft Edge", null)]
[TestCase("Microsoft Edge ---inprivate", "Microsoft Edge ---inprivate", null)]
[TestCase("Microsoft Edge -- -inprivate", "Microsoft Edge", "-inprivate")]
[TestCase("Microsoft Edge -inprivate", "Microsoft Edge", "-inprivate")]
[TestCase("Microsoft Edge /inprivate", "Microsoft Edge", "/inprivate")]
[TestCase("edge.exe --inprivate", "edge.exe", "--inprivate")]
[TestCase("edge.exe -- --inprivate", "edge.exe", "--inprivate")]
[TestCase("edge.exe", "edge.exe", null)]
[TestCase("edge", "edge", null)]
[TestCase("cmd /c \"ping 1.1.1.1\"", "cmd", "/c \"ping 1.1.1.1\"")]
public void ProgramArgumentParserTestsCanParseQuery(string inputQuery, string expectedProgram, string expectedProgramArguments)
{
// Arrange
var argumentParsers = new IProgramArgumentParser[]
{
new DoubleDashProgramArgumentParser(),
new InferedProgramArgumentParser(),
new NoArgumentsArgumentParser(),
};

// basic version of the Quey parser which can be found at Wox.Core.Plugin.QueryBuilder but did not want to create a project reference
var splittedSearchString = inputQuery?.Split(Query.TermSeparator, System.StringSplitOptions.RemoveEmptyEntries);
var cleanQuery = string.Join(Query.TermSeparator, splittedSearchString);
var query = new Query(cleanQuery, cleanQuery, splittedSearchString, string.Empty);

// Act
string program = null, programArguments = null;
foreach (var argumentParser in argumentParsers)
{
if (argumentParser.TryParse(query, out program, out programArguments))
{
break;
}
}

// Assert
Assert.AreEqual(expectedProgram, program);
Assert.AreEqual(expectedProgramArguments, programArguments);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ public void Win32AppsShouldSetNameAsTitleWhileCreatingResult()
StringMatcher.Instance = new StringMatcher();

// Act
var result = _cmderRunCommand.Result("cmder", mock.Object);
var result = _cmderRunCommand.Result("cmder", string.Empty, mock.Object);

// Assert
Assert.IsTrue(result.Title.Equals(_cmderRunCommand.Name, StringComparison.Ordinal));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Wox.Plugin;

namespace Microsoft.Plugin.Program
{
public interface IProgramArgumentParser
{
bool Enabled { get; }

bool TryParse(Query query, out string program, out string programArguments);
}
}
51 changes: 38 additions & 13 deletions src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

Expand All @@ -7,6 +7,7 @@
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Plugin.Program.ProgramArgumentParser;
using Microsoft.Plugin.Program.Programs;
using Microsoft.Plugin.Program.Storage;
using Wox.Infrastructure.Logger;
Expand All @@ -18,6 +19,15 @@ namespace Microsoft.Plugin.Program
{
public class Main : IPlugin, IPluginI18n, IContextMenu, ISavable, IReloadable, IDisposable
{
// The order of this array is important! The Parsers will be checked in order (index 0 to index Length-1) and the first parser which is able to parse the Query will be used
// NoArgumentsArgumentParser does always succeed and therefor should always be last/fallback
private static readonly IProgramArgumentParser[] _programArgumentParsers = new IProgramArgumentParser[]
{
new DoubleDashProgramArgumentParser(),
royvou marked this conversation as resolved.
Show resolved Hide resolved
new InferedProgramArgumentParser(),
new NoArgumentsArgumentParser(),
};

internal static ProgramPluginSettings Settings { get; set; }

private static PluginInitContext _context;
Expand Down Expand Up @@ -60,22 +70,37 @@ public void Save()

public List<Result> Query(Query query)
{
var results1 = _win32ProgramRepository.AsParallel()
.Where(p => p.Enabled)
.Select(p => p.Result(query.Search, _context.API));
foreach (var programArgumentParser in _programArgumentParsers)
{
if (!programArgumentParser.Enabled)
{
continue;
}

var results2 = _packageRepository.AsParallel()
.Where(p => p.Enabled)
.Select(p => p.Result(query.Search, _context.API));
if (!programArgumentParser.TryParse(query, out var program, out var programArguments))
{
continue;
}

var result = results1.Concat(results2).Where(r => r != null && r.Score > 0);
if (result.Any())
{
var maxScore = result.Max(x => x.Score);
result = result.Where(x => x.Score > Settings.MinScoreThreshold * maxScore);
var results1 = _win32ProgramRepository.AsParallel()
.Where(p => p.Enabled)
.Select(p => p.Result(program, programArguments, _context.API));

var results2 = _packageRepository.AsParallel()
.Where(p => p.Enabled)
.Select(p => p.Result(program, programArguments, _context.API));

var result = results1.Concat(results2).Where(r => r != null && r.Score > 0);
if (result.Any())
{
var maxScore = result.Max(x => x.Score);
result = result.Where(x => x.Score > Settings.MinScoreThreshold * maxScore);
}

return result.ToList();
}

return result.ToList();
return new List<Result>(0);
royvou marked this conversation as resolved.
Show resolved Hide resolved
}

public void Init(PluginInitContext context)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Linq;
using Wox.Plugin;

namespace Microsoft.Plugin.Program
{
public class DoubleDashProgramArgumentParser : IProgramArgumentParser
{
private const string DoubleDash = "--";

public bool Enabled { get; } = true;

public bool TryParse(Query query, out string program, out string programArguments)
{
if (!string.IsNullOrEmpty(query?.Search))
{
// First Argument is always (part of) the program, 2nd term is possibly a Program Argument
if (query.Terms.Length > 1)
{
for (var i = 1; i < query.Terms.Length; i++)
{
if (!string.Equals(query.Terms[i], DoubleDash, StringComparison.Ordinal))
{
continue;
}

program = string.Join(Query.TermSeparator, query.Terms.Take(i));
programArguments = string.Join(Query.TermSeparator, query.Terms.Skip(i + 1));
return true;
}
}
}

program = null;
programArguments = null;
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Linq;
using System.Text.RegularExpressions;
using Wox.Plugin;

namespace Microsoft.Plugin.Program
{
public class InferedProgramArgumentParser : IProgramArgumentParser
{
private static readonly Regex ArgumentPrefixRegex = new Regex("^(-|--|/)[a-zA-Z]+", RegexOptions.Compiled);

public bool Enabled { get; } = true;

public bool TryParse(Query query, out string program, out string programArguments)
{
if (!string.IsNullOrEmpty(query?.Search))
{
// First Argument is always (part of) the program, 2nd term is possibly a Program Argument
if (query.Terms.Length > 1)
{
for (var i = 1; i < query.Terms.Length; i++)
{
if (!ArgumentPrefixRegex.IsMatch(query.Terms[i]))
{
continue;
}

program = string.Join(Query.TermSeparator, query.Terms.Take(i));
programArguments = string.Join(Query.TermSeparator, query.Terms.Skip(i));
return true;
}
}
}

program = null;
programArguments = null;
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Wox.Plugin;

namespace Microsoft.Plugin.Program.ProgramArgumentParser
{
public class NoArgumentsArgumentParser : IProgramArgumentParser
{
public bool Enabled { get; } = true;

public bool TryParse(Query query, out string program, out string programArguments)
{
program = query?.Search;
programArguments = null;
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public interface IProgram
{
List<ContextMenuResult> ContextMenus(IPublicAPI api);

Result Result(string query, IPublicAPI api);
Result Result(string query, string queryArguments, IPublicAPI api);

string UniqueIdentifier { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private static string SetSubtitle()
return Properties.Resources.powertoys_run_plugin_program_packaged_application;
}

public Result Result(string query, IPublicAPI api)
public Result Result(string query, string queryArguments, IPublicAPI api)
{
if (api == null)
{
Expand All @@ -100,7 +100,7 @@ public Result Result(string query, IPublicAPI api)
ContextData = this,
Action = e =>
{
Launch(api);
Launch(api, queryArguments);
return true;
},
};
Expand Down Expand Up @@ -195,16 +195,15 @@ public List<ContextMenuResult> ContextMenus(IPublicAPI api)
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive, and showing the user an error message")]
private async void Launch(IPublicAPI api)
private async void Launch(IPublicAPI api, string queryArguments)
{
var appManager = new ApplicationActivationHelper.ApplicationActivationManager();
const string noArgs = "";
const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
await Task.Run(() =>
{
try
{
appManager.ActivateApplication(UserModelId, noArgs, noFlags, out uint unusedPid);
appManager.ActivateApplication(UserModelId, queryArguments, noFlags, out var unusedPid);
}
catch (Exception)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a UTF8 files, but the other files in this project are UTF8 + BOM, seems like a sensible change to add?

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

Expand Down Expand Up @@ -179,7 +179,7 @@ public bool QueryEqualsNameForRunCommands(string query)
return true;
}

public Result Result(string query, IPublicAPI api)
public Result Result(string query, string queryArguments, IPublicAPI api)
{
if (api == null)
{
Expand Down Expand Up @@ -225,6 +225,7 @@ public Result Result(string query, IPublicAPI api)
FileName = LnkResolvedPath ?? FullPath,
WorkingDirectory = ParentDirectory,
UseShellExecute = true,
Arguments = queryArguments,
};

Main.StartProcess(Process.Start, info);
Expand Down