Skip to content

Commit

Permalink
[PowerToys Run] Add Suport for Commandline arguments in Program Plugin (
Browse files Browse the repository at this point in the history
#5791)

* Implemented possibility to add commandline arguments in the Program Plugin

* Add missing return statement inc ommandArgumentParser loop

* Fix typos

* Fix Additional Typo

* Changed -c to /c to make it a valid cmd argument

* Added small comment about importance of order in _programArgumentParsers

Co-authored-by: Roy <royvou@hotmailcom>
  • Loading branch information
royvou and Roy committed Sep 17, 2020
1 parent 923972d commit b3833fc
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 22 deletions.
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(),
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);
}

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
// 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

0 comments on commit b3833fc

Please sign in to comment.