mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
[PowerToys Run] Add Suport for Commandline arguments in Program Plugin (#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>
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -547,7 +547,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
|||||||
StringMatcher.Instance = new StringMatcher();
|
StringMatcher.Instance = new StringMatcher();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = _cmderRunCommand.Result("cmder", mock.Object);
|
var result = _cmderRunCommand.Result("cmder", string.Empty, mock.Object);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.IsTrue(result.Title.Equals(_cmderRunCommand.Name, StringComparison.Ordinal));
|
Assert.IsTrue(result.Title.Equals(_cmderRunCommand.Name, StringComparison.Ordinal));
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
// Copyright (c) Microsoft Corporation
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Plugin.Program.ProgramArgumentParser;
|
||||||
using Microsoft.Plugin.Program.Programs;
|
using Microsoft.Plugin.Program.Programs;
|
||||||
using Microsoft.Plugin.Program.Storage;
|
using Microsoft.Plugin.Program.Storage;
|
||||||
using Wox.Infrastructure.Logger;
|
using Wox.Infrastructure.Logger;
|
||||||
@@ -18,6 +19,15 @@ namespace Microsoft.Plugin.Program
|
|||||||
{
|
{
|
||||||
public class Main : IPlugin, IPluginI18n, IContextMenu, ISavable, IReloadable, IDisposable
|
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; }
|
internal static ProgramPluginSettings Settings { get; set; }
|
||||||
|
|
||||||
private static PluginInitContext _context;
|
private static PluginInitContext _context;
|
||||||
@@ -60,22 +70,37 @@ namespace Microsoft.Plugin.Program
|
|||||||
|
|
||||||
public List<Result> Query(Query query)
|
public List<Result> Query(Query query)
|
||||||
{
|
{
|
||||||
var results1 = _win32ProgramRepository.AsParallel()
|
foreach (var programArgumentParser in _programArgumentParsers)
|
||||||
.Where(p => p.Enabled)
|
|
||||||
.Select(p => p.Result(query.Search, _context.API));
|
|
||||||
|
|
||||||
var results2 = _packageRepository.AsParallel()
|
|
||||||
.Where(p => p.Enabled)
|
|
||||||
.Select(p => p.Result(query.Search, _context.API));
|
|
||||||
|
|
||||||
var result = results1.Concat(results2).Where(r => r != null && r.Score > 0);
|
|
||||||
if (result.Any())
|
|
||||||
{
|
{
|
||||||
var maxScore = result.Max(x => x.Score);
|
if (!programArgumentParser.Enabled)
|
||||||
result = result.Where(x => x.Score > Settings.MinScoreThreshold * maxScore);
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!programArgumentParser.TryParse(query, out var program, out var programArguments))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
public void Init(PluginInitContext context)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ namespace Microsoft.Plugin.Program.Programs
|
|||||||
{
|
{
|
||||||
List<ContextMenuResult> ContextMenus(IPublicAPI api);
|
List<ContextMenuResult> ContextMenus(IPublicAPI api);
|
||||||
|
|
||||||
Result Result(string query, IPublicAPI api);
|
Result Result(string query, string queryArguments, IPublicAPI api);
|
||||||
|
|
||||||
string UniqueIdentifier { get; set; }
|
string UniqueIdentifier { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ namespace Microsoft.Plugin.Program.Programs
|
|||||||
return Properties.Resources.powertoys_run_plugin_program_packaged_application;
|
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)
|
if (api == null)
|
||||||
{
|
{
|
||||||
@@ -100,7 +100,7 @@ namespace Microsoft.Plugin.Program.Programs
|
|||||||
ContextData = this,
|
ContextData = this,
|
||||||
Action = e =>
|
Action = e =>
|
||||||
{
|
{
|
||||||
Launch(api);
|
Launch(api, queryArguments);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -195,16 +195,15 @@ namespace Microsoft.Plugin.Program.Programs
|
|||||||
}
|
}
|
||||||
|
|
||||||
[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")]
|
[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();
|
var appManager = new ApplicationActivationHelper.ApplicationActivationManager();
|
||||||
const string noArgs = "";
|
|
||||||
const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
|
const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
appManager.ActivateApplication(UserModelId, noArgs, noFlags, out uint unusedPid);
|
appManager.ActivateApplication(UserModelId, queryArguments, noFlags, out var unusedPid);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) Microsoft Corporation
|
// Copyright (c) Microsoft Corporation
|
||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ namespace Microsoft.Plugin.Program.Programs
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result Result(string query, IPublicAPI api)
|
public Result Result(string query, string queryArguments, IPublicAPI api)
|
||||||
{
|
{
|
||||||
if (api == null)
|
if (api == null)
|
||||||
{
|
{
|
||||||
@@ -225,6 +225,7 @@ namespace Microsoft.Plugin.Program.Programs
|
|||||||
FileName = LnkResolvedPath ?? FullPath,
|
FileName = LnkResolvedPath ?? FullPath,
|
||||||
WorkingDirectory = ParentDirectory,
|
WorkingDirectory = ParentDirectory,
|
||||||
UseShellExecute = true,
|
UseShellExecute = true,
|
||||||
|
Arguments = queryArguments,
|
||||||
};
|
};
|
||||||
|
|
||||||
Main.StartProcess(Process.Start, info);
|
Main.StartProcess(Process.Start, info);
|
||||||
|
|||||||
Reference in New Issue
Block a user