Skip to content
Closed
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
9 changes: 6 additions & 3 deletions Flow.Launcher.Core/Plugin/ExecutablePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ public ExecutablePlugin(string filename)
RedirectStandardOutput = true,
RedirectStandardError = true
};
// required initialisation for below request calls

// required initialisation for below request calls
_startInfo.ArgumentList.Add(string.Empty);
}

protected override Task<Stream> RequestAsync(JsonRPCRequestModel request, CancellationToken token = default)
protected override Task<Stream> RequestAsync(
JsonRPCRequestModel request,
CancellationToken token = default,
bool ignoreEmptyResponse = false)
{
// since this is not static, request strings will build up in ArgumentList if index is not specified
_startInfo.ArgumentList[0] = request.ToString();
Expand Down
35 changes: 29 additions & 6 deletions Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ namespace Flow.Launcher.Core.Plugin
/// Represent the plugin that using JsonPRC
/// every JsonRPC plugin should has its own plugin instance
/// </summary>
internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable, IPathSelected
{
protected PluginInitContext context;
public const string JsonRPC = "JsonRPC";
Expand All @@ -44,7 +44,11 @@ internal abstract class JsonRPCPlugin : IAsyncPlugin, IContextMenu, ISettingProv
/// The language this JsonRPCPlugin support
/// </summary>
public abstract string SupportedLanguage { get; set; }
protected abstract Task<Stream> RequestAsync(JsonRPCRequestModel rpcRequest, CancellationToken token = default);
protected abstract Task<Stream> RequestAsync(
JsonRPCRequestModel rpcRequest,
CancellationToken token = default,
bool ignoreEmptyResponse = false);

protected abstract string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default);

private static readonly RecyclableMemoryStreamManager BufferManager = new();
Expand Down Expand Up @@ -238,7 +242,10 @@ protected string Execute(ProcessStartInfo startInfo)
}
}

protected async Task<Stream> ExecuteAsync(ProcessStartInfo startInfo, CancellationToken token = default)
protected async Task<Stream> ExecuteAsync(
ProcessStartInfo startInfo,
CancellationToken token = default,
bool ignoreEmptyResponse = false)
{
Process process = null;
bool disposed = false;
Expand All @@ -265,7 +272,7 @@ protected async Task<Stream> ExecuteAsync(ProcessStartInfo startInfo, Cancellati

try
{
// token expire won't instantly trigger the exception,
// token expire won't instantly trigger the exception,
// manually kill process at before
await source.CopyToAsync(buffer, token);
}
Expand All @@ -281,10 +288,13 @@ protected async Task<Stream> ExecuteAsync(ProcessStartInfo startInfo, Cancellati

if (buffer.Length == 0)
{
var errorMessage = process.StandardError.EndOfStream ?
var endOfStream = process.StandardError.EndOfStream;
var errorMessage = endOfStream ?
"Empty JSONRPC Response" :
await process.StandardError.ReadToEndAsync();
throw new InvalidDataException($"{context.CurrentPluginMetadata.Name}|{errorMessage}");

if (!endOfStream || (endOfStream && !ignoreEmptyResponse))
throw new InvalidDataException($"{context.CurrentPluginMetadata.Name}|{errorMessage}");
}

if (!process.StandardError.EndOfStream)
Expand Down Expand Up @@ -534,5 +544,18 @@ public void UpdateSettings(Dictionary<string, object> settings)
}
}
}

public async Task PathSelected(string filePath, string hotkey)
{
var request = new JsonRPCRequestModel
{
Method = "PathSelected",
Parameters = new object[]
{
filePath, hotkey
}
};
await RequestAsync(request, ignoreEmptyResponse: true);
}
}
}
17 changes: 15 additions & 2 deletions Flow.Launcher.Core/Plugin/PluginManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -26,7 +26,6 @@ public static class PluginManager

public static IPublicAPI API { private set; get; }

// todo happlebao, this should not be public, the indicator function should be embeded
public static PluginsSettings Settings;
private static List<PluginMetadata> _metadatas;

Expand Down Expand Up @@ -179,6 +178,20 @@ public static ICollection<PluginPair> ValidPluginsForQuery(Query query)
}
}

public static async Task PublishPathSelectedFromResult(string selectedPath, string hotkey)
{
await Task.WhenAll(AllPlugins.Select(pluginPair => pluginPair.Plugin switch
{
IPathSelected p
when pluginPair
.Metadata
.Listeners
.Any(x => x.ContainsKey(hotkey) && x.ContainsValue("IPathSelected"))
=> p.PathSelected(selectedPath, hotkey),
_ => Task.CompletedTask,
}));
}

public static async Task<List<Result>> QueryForPluginAsync(PluginPair pair, Query query, CancellationToken token)
{
var results = new List<Result>();
Expand Down
7 changes: 5 additions & 2 deletions Flow.Launcher.Core/Plugin/PythonPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ public PythonPlugin(string filename)
_startInfo.ArgumentList.Add("-B");
}

protected override Task<Stream> RequestAsync(JsonRPCRequestModel request, CancellationToken token = default)
protected override Task<Stream> RequestAsync(
JsonRPCRequestModel request,
CancellationToken token = default,
bool ignoreEmptyResponse = false)
{
_startInfo.ArgumentList[2] = request.ToString();

return ExecuteAsync(_startInfo, token);
return ExecuteAsync(_startInfo, token, ignoreEmptyResponse: ignoreEmptyResponse);
}

protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
Expand Down
13 changes: 13 additions & 0 deletions Flow.Launcher.Plugin/Interfaces/IPathSelected.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Flow.Launcher.Plugin
{
public interface IPathSelected : IFeatures
{
Task PathSelected(string path, string hotkey);
}
}
4 changes: 3 additions & 1 deletion Flow.Launcher.Plugin/PluginMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ internal set

public List<string> ActionKeywords { get; set; }

public List<Dictionary<string, string>> Listeners { get; set;}

public string IcoPath { get; set;}

public override string ToString()
{
return Name;
Expand Down
2 changes: 1 addition & 1 deletion Flow.Launcher/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="Escape" Command="{Binding EscCommand}" />
<KeyBinding Key="F1" Command="{Binding StartHelpCommand}" />
<KeyBinding Key="F1" Command="{Binding F1SelectedCommand}" />
<KeyBinding Key="F5" Command="{Binding ReloadPluginDataCommand}" />
<KeyBinding Key="Tab" Command="{Binding AutocompleteQueryCommand}" />
<KeyBinding
Expand Down
29 changes: 29 additions & 0 deletions Flow.Launcher/ViewModel/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,33 @@ private void InitializeKeyCommands()

SelectPrevPageCommand = new RelayCommand(_ => { SelectedResults.SelectPrevPage(); });

F1SelectedCommand = new RelayCommand(_ =>
{
var results = SelectedResults;
var result = results.SelectedItem?.Result;

if (result is null)
return;

var selectedPath = string.Empty;

if (File.Exists(result.Title) || Directory.Exists(result.Title))
{
selectedPath = result.Title;
}
else if (File.Exists(result.SubTitle) || Directory.Exists(result.SubTitle))
{
selectedPath = result.SubTitle;
}
else if (!string.IsNullOrEmpty(result.IcoPath))
{
selectedPath = result.IcoPath;
}

PluginManager.PublishPathSelectedFromResult(selectedPath, "f1");

});

SelectFirstResultCommand = new RelayCommand(_ => SelectedResults.SelectFirstResult());

StartHelpCommand = new RelayCommand(_ =>
Expand Down Expand Up @@ -430,6 +457,8 @@ private ResultsViewModel SelectedResults

public ICommand AutocompleteQueryCommand { get; set; }

public ICommand F1SelectedCommand { get; set; }

public string OpenResultCommandModifiers { get; private set; }

public string Image => Constant.QueryTextBoxIconImagePath;
Expand Down
10 changes: 9 additions & 1 deletion Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Flow.Launcher.Plugin.Explorer
{
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n
public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n, IPathSelected
{
internal PluginInitContext Context { get; set; }

Expand All @@ -28,6 +28,14 @@ public Control CreateSettingPanel()
return new ExplorerSettings(viewModel);
}

public Task PathSelected(string path, string hotkey)
{
//checked the hotkey if is ctrl+c, then
//copy file to clipboard.
Context.API.ShowMsg("Success", string.Format("You have selected path \"{0}\", with hotkey \"{1}\"", path, hotkey));
return Task.CompletedTask;
}

public Task InitAsync(PluginInitContext context)
{
Context = context;
Expand Down
1 change: 1 addition & 0 deletions Plugins/Flow.Launcher.Plugin.Explorer/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"*",
"*"
],
"Listeners": [{"f1": "IPathSelected"}],
"Name": "Explorer",
"Description": "Search and manage files and folders. Explorer utilises Windows Index Search",
"Author": "Jeremy Wu",
Expand Down