mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Implement JSONRPC V2 Draft
This commit is contained in:
parent
c74eafb9d5
commit
67d1b896b1
11 changed files with 276 additions and 128 deletions
|
|
@ -6,7 +6,7 @@ using Flow.Launcher.Plugin;
|
|||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
internal class ExecutablePlugin : JsonRPCPlugin
|
||||
internal class ExecutablePlugin : JsonRpcPlugin
|
||||
{
|
||||
private readonly ProcessStartInfo _startInfo;
|
||||
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
|
||||
|
|
|
|||
|
|
@ -21,67 +21,36 @@ using System.Text.Json;
|
|||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
public class JsonRPCErrorModel
|
||||
{
|
||||
public int Code { get; set; }
|
||||
public record JsonRPCRequestMessage(PluginMetadata PluginMetadata, IAsyncEnumerable<JsonRPCRequestModel> Requests);
|
||||
public record JsonRPCBase(int Id, JsonRPCErrorModel Error = default);
|
||||
public record JsonRPCErrorModel(int Code, string Message, string Data);
|
||||
|
||||
public string Message { get; set; }
|
||||
public record JsonRPCResponseModel(int Id, JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
|
||||
public record JsonRPCQueryResponseModel(int Id,
|
||||
[property: JsonPropertyName("result")] List<JsonRPCResult> Result,
|
||||
Dictionary<string, object> SettingsChange = null,
|
||||
string DebugMessage = "",
|
||||
JsonRPCErrorModel Error = default) : JsonRPCResponseModel(Id, Error);
|
||||
|
||||
public string Data { get; set; }
|
||||
}
|
||||
public record JsonRPCRequestModel(int Id,
|
||||
string Method,
|
||||
object[] Parameters,
|
||||
Dictionary<string, object> Settings = default,
|
||||
JsonRPCErrorModel Error = default) : JsonRPCBase(Id, Error);
|
||||
|
||||
|
||||
public class JsonRPCResponseModel
|
||||
{
|
||||
public string Result { get; set; }
|
||||
|
||||
public JsonRPCErrorModel Error { get; set; }
|
||||
}
|
||||
|
||||
public class JsonRPCQueryResponseModel : JsonRPCResponseModel
|
||||
{
|
||||
[JsonPropertyName("result")]
|
||||
public new List<JsonRPCResult> Result { get; set; }
|
||||
|
||||
public Dictionary<string, object> SettingsChange { get; set; }
|
||||
|
||||
public string DebugMessage { get; set; }
|
||||
}
|
||||
|
||||
public class JsonRPCRequestModel
|
||||
{
|
||||
public string Method { get; set; }
|
||||
|
||||
public object[] Parameters { get; set; }
|
||||
|
||||
public Dictionary<string, object> Settings { get; set; }
|
||||
|
||||
private static readonly JsonSerializerOptions options = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
public override string ToString()
|
||||
{
|
||||
return JsonSerializer.Serialize(this, options);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Json RPC Request that Flow Launcher sent to client
|
||||
/// </summary>
|
||||
public class JsonRPCServerRequestModel : JsonRPCRequestModel
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Json RPC Request(in query response) that client sent to Flow Launcher
|
||||
/// </summary>
|
||||
public class JsonRPCClientRequestModel : JsonRPCRequestModel
|
||||
{
|
||||
public bool DontHideAfterAction { get; set; }
|
||||
}
|
||||
|
||||
public record JsonRPCClientRequestModel(
|
||||
int Id,
|
||||
string Method,
|
||||
object[] Parameters,
|
||||
Dictionary<string, object> Settings,
|
||||
bool DontHideAfterAction = false,
|
||||
JsonRPCErrorModel Error = default) : JsonRPCRequestModel(Id, Method, Parameters, Settings, Error);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represent the json-rpc result item that client send to Flow Launcher
|
||||
/// Typically, we will send back this request model to client after user select the result item
|
||||
|
|
|
|||
21
Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
Normal file
21
Flow.Launcher.Core/Plugin/JsonRPCModelContext.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
// TODO: After Upgrading to .Net 7, adding Source Generating Context for IAsyncEnumerable JsonRPCMessage
|
||||
|
||||
[JsonSerializable(typeof(JsonRPCQueryResponseModel))]
|
||||
public partial class JsonRPCQueryResponseModelContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(JsonRPCRequestModel))]
|
||||
public partial class JsonRPCRequestModelContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(JsonRPCClientRequestModel))]
|
||||
public partial class JsonRPCClientRequestModelContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -29,10 +29,10 @@ 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
|
||||
{
|
||||
protected PluginInitContext context;
|
||||
public const string JsonRPC = "JsonRPC";
|
||||
protected PluginInitContext Context;
|
||||
public const string JsonRpc = "JsonRPC";
|
||||
|
||||
/// <summary>
|
||||
/// The language this JsonRPCPlugin support
|
||||
|
|
@ -43,19 +43,16 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
private static readonly RecyclableMemoryStreamManager BufferManager = new();
|
||||
|
||||
private string SettingConfigurationPath => Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
|
||||
private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name, "Settings.json");
|
||||
private int RequestId { get; set; }
|
||||
|
||||
private string SettingConfigurationPath => Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
|
||||
private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name, "Settings.json");
|
||||
|
||||
public List<Result> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
var request = new JsonRPCRequestModel
|
||||
{
|
||||
Method = "context_menu",
|
||||
Parameters = new[]
|
||||
{
|
||||
selectedResult.ContextData
|
||||
}
|
||||
};
|
||||
var request = new JsonRPCRequestModel(RequestId++,
|
||||
"context_menu",
|
||||
new[] { selectedResult.ContextData });
|
||||
var output = Request(request);
|
||||
return DeserializedResult(output);
|
||||
}
|
||||
|
|
@ -113,7 +110,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
if (!string.IsNullOrEmpty(queryResponseModel.DebugMessage))
|
||||
{
|
||||
context.API.ShowMsg(queryResponseModel.DebugMessage);
|
||||
Context.API.ShowMsg(queryResponseModel.DebugMessage);
|
||||
}
|
||||
|
||||
foreach (var result in queryResponseModel.Result)
|
||||
|
|
@ -281,7 +278,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
case (0, 0):
|
||||
const string errorMessage = "Empty JSON-RPC Response.";
|
||||
Log.Warn($"|{nameof(JsonRPCPlugin)}.{nameof(ExecuteAsync)}|{errorMessage}");
|
||||
Log.Warn($"|{nameof(JsonRpcPlugin)}.{nameof(ExecuteAsync)}|{errorMessage}");
|
||||
break;
|
||||
case (_, not 0):
|
||||
throw new InvalidDataException(Encoding.UTF8.GetString(errorBuffer.ToArray())); // The process has exited with an error message
|
||||
|
|
@ -295,20 +292,15 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||
{
|
||||
var request = new JsonRPCRequestModel
|
||||
{
|
||||
Method = "query",
|
||||
Parameters = new object[]
|
||||
{
|
||||
query.Search
|
||||
},
|
||||
Settings = Settings
|
||||
};
|
||||
var request = new JsonRPCRequestModel(RequestId++,
|
||||
"query",
|
||||
new object[]{ query.Search },
|
||||
Settings);
|
||||
var output = await RequestAsync(request, token);
|
||||
return await DeserializedResultAsync(output);
|
||||
}
|
||||
|
||||
public async Task InitSettingAsync()
|
||||
private async Task InitSettingAsync()
|
||||
{
|
||||
if (!File.Exists(SettingConfigurationPath))
|
||||
return;
|
||||
|
|
@ -337,7 +329,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
public virtual async Task InitAsync(PluginInitContext context)
|
||||
{
|
||||
this.context = context;
|
||||
this.Context = context;
|
||||
await InitSettingAsync();
|
||||
}
|
||||
private static readonly Thickness settingControlMargin = new(10, 4, 10, 4);
|
||||
|
|
@ -483,7 +475,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
if (Settings != null)
|
||||
{
|
||||
Helper.ValidateDirectory(Path.Combine(DataLocation.PluginSettingsDirectory, context.CurrentPluginMetadata.Name));
|
||||
Helper.ValidateDirectory(Path.Combine(DataLocation.PluginSettingsDirectory, Context.CurrentPluginMetadata.Name));
|
||||
File.WriteAllText(SettingPath, JsonSerializer.Serialize(Settings, settingSerializeOption));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
100
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
Normal file
100
Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
public abstract class JsonRpcPluginV2 : IAsyncPlugin, IContextMenu, ISettingProvider, ISavable
|
||||
{
|
||||
public abstract string SupportedLanguage { get; set; }
|
||||
|
||||
public const string JsonRpc = "JsonRPC";
|
||||
protected abstract Stream InputStream { get; set; }
|
||||
protected abstract Stream OutputStream { get; set; }
|
||||
protected abstract StreamReader ErrorStream { get; set; }
|
||||
|
||||
protected Channel<JsonRPCRequestModel> InputMessageChannel { get; set; }
|
||||
|
||||
private (Task SendTask, Task ReceiveTask) MessageTask { get; set; }
|
||||
private CancellationTokenSource MessageCancellationTokenSource { get; set; }
|
||||
|
||||
protected int RequestId;
|
||||
|
||||
private ConcurrentDictionary<int, TaskCompletionSource<JsonRPCQueryResponseModel>> RequestTaskDictionary { get; } = new();
|
||||
|
||||
// TODO: Switch to Async Task
|
||||
private async void ReceiveMessageAsync(CancellationToken token)
|
||||
{
|
||||
var response =
|
||||
JsonSerializer.DeserializeAsyncEnumerable<JsonRPCQueryResponseModel>(OutputStream, cancellationToken: token);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(response);
|
||||
|
||||
await foreach (var message in response.WithCancellation(token))
|
||||
{
|
||||
if (!RequestTaskDictionary.TryGetValue(message.Id, out var task))
|
||||
{
|
||||
// Either Task is already handled or it is a invalid resopnse.
|
||||
continue;
|
||||
}
|
||||
RequestTaskDictionary.Remove(message.Id, out _);
|
||||
task.TrySetResult(message);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Switch to Async Task
|
||||
private async void SendMessageAsync(PluginMetadata metadata, CancellationToken token)
|
||||
{
|
||||
var fullMessage = new JsonRPCRequestMessage(metadata, InputMessageChannel.Reader.ReadAllAsync(token));
|
||||
await JsonSerializer.SerializeAsync(InputStream, fullMessage, cancellationToken: token);
|
||||
}
|
||||
|
||||
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
||||
{
|
||||
int currentRequestId = Interlocked.Add(ref RequestId, 1);
|
||||
var message = new JsonRPCRequestModel(currentRequestId, "query", new object[]
|
||||
{
|
||||
query
|
||||
});
|
||||
await InputMessageChannel.Writer.WriteAsync(message, token);
|
||||
await Task.Delay(50);
|
||||
await InputStream.FlushAsync();
|
||||
var task = new TaskCompletionSource<JsonRPCQueryResponseModel>();
|
||||
RequestTaskDictionary[currentRequestId] = task;
|
||||
var result = await task.Task;
|
||||
//TODO: Parse Result
|
||||
return new List<Result>();
|
||||
}
|
||||
public virtual Task InitAsync(PluginInitContext context)
|
||||
{
|
||||
InputMessageChannel = Channel.CreateUnbounded<JsonRPCRequestModel>();
|
||||
MessageCancellationTokenSource = new CancellationTokenSource();
|
||||
SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token);
|
||||
ReceiveMessageAsync(MessageCancellationTokenSource.Token);
|
||||
// MessageTask =
|
||||
// (SendMessageAsync(context.CurrentPluginMetadata, MessageCancellationTokenSource.Token),
|
||||
// ReceiveMessageAsync(MessageCancellationTokenSource.Token));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public List<Result> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
public Control CreateSettingPanel()
|
||||
{
|
||||
// TODO: Implement CreateSettingPanel
|
||||
return new Control();
|
||||
}
|
||||
public void Save()
|
||||
{
|
||||
// TODO: Save settings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -83,7 +83,10 @@ namespace Flow.Launcher.Core.Plugin
|
|||
return;
|
||||
}
|
||||
|
||||
plugins.Add(new PluginPair {Plugin = plugin, Metadata = metadata});
|
||||
plugins.Add(new PluginPair
|
||||
{
|
||||
Plugin = plugin, Metadata = metadata
|
||||
});
|
||||
});
|
||||
metadata.InitTime += milliseconds;
|
||||
}
|
||||
|
|
@ -110,14 +113,14 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
public static IEnumerable<PluginPair> PythonPlugins(List<PluginMetadata> source, PluginsSettings settings)
|
||||
{
|
||||
if (!source.Any(o => o.Language.ToUpper() == AllowedLanguage.Python))
|
||||
if (!source.Any(o => o.Language.ToUpper() is AllowedLanguage.Python or AllowedLanguage.PythonV2))
|
||||
return new List<PluginPair>();
|
||||
|
||||
if (!string.IsNullOrEmpty(settings.PythonDirectory) && FilesFolders.LocationExists(settings.PythonDirectory))
|
||||
return SetPythonPathForPluginPairs(source, Path.Combine(settings.PythonDirectory, PythonExecutable));
|
||||
|
||||
var pythonPath = string.Empty;
|
||||
|
||||
|
||||
if (MessageBox.Show("Flow detected you have installed Python plugins, which " +
|
||||
"will need Python to run. Would you like to download Python? " +
|
||||
Environment.NewLine + Environment.NewLine +
|
||||
|
|
@ -185,17 +188,22 @@ namespace Flow.Launcher.Core.Plugin
|
|||
return SetPythonPathForPluginPairs(source, pythonPath);
|
||||
}
|
||||
|
||||
private static IEnumerable<PluginPair> SetPythonPathForPluginPairs(List<PluginMetadata> source, string pythonPath)
|
||||
=> source
|
||||
.Where(o => o.Language.ToUpper() == AllowedLanguage.Python)
|
||||
private static IEnumerable<PluginPair> SetPythonPathForPluginPairs(IEnumerable<PluginMetadata> source, string pythonPath)
|
||||
=> source
|
||||
.Where(o => o.Language.ToUpper() is AllowedLanguage.Python or AllowedLanguage.PythonV2)
|
||||
.Select(metadata => new PluginPair
|
||||
{
|
||||
Plugin = new PythonPlugin(pythonPath),
|
||||
Plugin = metadata.Language.ToUpper() switch
|
||||
{
|
||||
AllowedLanguage.Python => new PythonPlugin(pythonPath),
|
||||
AllowedLanguage.PythonV2 => new PythonPluginV2(pythonPath),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
},
|
||||
Metadata = metadata
|
||||
})
|
||||
.ToList();
|
||||
|
||||
public static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source)
|
||||
public static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source)
|
||||
{
|
||||
return source
|
||||
.Where(o => o.Language.ToUpper() == AllowedLanguage.Executable)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ using Flow.Launcher.Plugin;
|
|||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
internal class PythonPlugin : JsonRPCPlugin
|
||||
internal class PythonPlugin : JsonRpcPlugin
|
||||
{
|
||||
private readonly ProcessStartInfo _startInfo;
|
||||
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
|
||||
|
|
@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
};
|
||||
|
||||
// temp fix for issue #667
|
||||
var path = Path.Combine(Constant.ProgramDirectory, JsonRPC);
|
||||
var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
|
||||
_startInfo.EnvironmentVariables["PYTHONPATH"] = path;
|
||||
|
||||
_startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
|
||||
|
|
@ -47,7 +47,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
|
||||
{
|
||||
_startInfo.ArgumentList[2] = rpcRequest.ToString();
|
||||
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
|
||||
_startInfo.WorkingDirectory = Context.CurrentPluginMetadata.PluginDirectory;
|
||||
// TODO: Async Action
|
||||
return Execute(_startInfo);
|
||||
}
|
||||
|
|
|
|||
63
Flow.Launcher.Core/Plugin/PythonPluginV2.cs
Normal file
63
Flow.Launcher.Core/Plugin/PythonPluginV2.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Core.Plugin
|
||||
{
|
||||
public class PythonPluginV2 : JsonRpcPluginV2
|
||||
{
|
||||
private readonly ProcessStartInfo _startInfo;
|
||||
private Process _process;
|
||||
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
|
||||
|
||||
protected override Stream InputStream { get; set; }
|
||||
protected override Stream OutputStream { get; set; }
|
||||
protected override StreamReader ErrorStream { get; set; }
|
||||
|
||||
public PythonPluginV2(string filename)
|
||||
{
|
||||
_startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = filename,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true
|
||||
};
|
||||
|
||||
// temp fix for issue #667
|
||||
var path = Path.Combine(Constant.ProgramDirectory, JsonRpc);
|
||||
_startInfo.EnvironmentVariables["PYTHONPATH"] = path;
|
||||
|
||||
_startInfo.EnvironmentVariables["FLOW_VERSION"] = Constant.Version;
|
||||
_startInfo.EnvironmentVariables["FLOW_PROGRAM_DIRECTORY"] = Constant.ProgramDirectory;
|
||||
_startInfo.EnvironmentVariables["FLOW_APPLICATION_DIRECTORY"] = Constant.ApplicationDirectory;
|
||||
|
||||
|
||||
//Add -B flag to tell python don't write .py[co] files. Because .pyc contains location infos which will prevent python portable
|
||||
_startInfo.ArgumentList.Add("-B");
|
||||
}
|
||||
|
||||
|
||||
public override async Task InitAsync(PluginInitContext context)
|
||||
{
|
||||
_startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
|
||||
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
|
||||
|
||||
_process = Process.Start(_startInfo);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(_process);
|
||||
|
||||
InputStream = _process.StandardInput.BaseStream;
|
||||
OutputStream = _process.StandardOutput.BaseStream;
|
||||
ErrorStream = _process.StandardError;
|
||||
|
||||
await base.InitAsync(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
namespace Flow.Launcher.Plugin
|
||||
using System;
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Allowed plugin languages
|
||||
|
|
@ -8,34 +10,27 @@
|
|||
/// <summary>
|
||||
/// Python
|
||||
/// </summary>
|
||||
public static string Python
|
||||
{
|
||||
get { return "PYTHON"; }
|
||||
}
|
||||
public const string Python = "PYTHON";
|
||||
|
||||
/// <summary>
|
||||
/// Python V2
|
||||
/// </summary>
|
||||
public const string PythonV2 = "PYTHON_V2";
|
||||
|
||||
/// <summary>
|
||||
/// C#
|
||||
/// </summary>
|
||||
public static string CSharp
|
||||
{
|
||||
get { return "CSHARP"; }
|
||||
}
|
||||
public const string CSharp = "CSHARP";
|
||||
|
||||
/// <summary>
|
||||
/// F#
|
||||
/// </summary>
|
||||
public static string FSharp
|
||||
{
|
||||
get { return "FSHARP"; }
|
||||
}
|
||||
public const string FSharp = "FSHARP";
|
||||
|
||||
/// <summary>
|
||||
/// Standard .exe
|
||||
/// </summary>
|
||||
public static string Executable
|
||||
{
|
||||
get { return "EXECUTABLE"; }
|
||||
}
|
||||
public const string Executable = "EXECUTABLE";
|
||||
|
||||
/// <summary>
|
||||
/// Determines if this language is a .NET language
|
||||
|
|
@ -56,8 +51,9 @@
|
|||
public static bool IsAllowed(string language)
|
||||
{
|
||||
return IsDotNet(language)
|
||||
|| language.ToUpper() == Python.ToUpper()
|
||||
|| language.ToUpper() == Executable.ToUpper();
|
||||
|| String.Equals(language, Python, StringComparison.CurrentCultureIgnoreCase)
|
||||
|| String.Equals(language, PythonV2, StringComparison.CurrentCultureIgnoreCase)
|
||||
|| String.Equals(language, Executable, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
|
|
@ -71,26 +72,31 @@ namespace Flow.Launcher.Plugin
|
|||
|
||||
public string ActionKeyword { get; init; }
|
||||
|
||||
[JsonIgnore]
|
||||
/// <summary>
|
||||
/// Return first search split by space if it has
|
||||
/// </summary>
|
||||
public string FirstSearch => SplitSearch(0);
|
||||
|
||||
|
||||
[JsonIgnore]
|
||||
private string _secondToEndSearch;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// strings from second search (including) to last search
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string SecondToEndSearch => SearchTerms.Length > 1 ? (_secondToEndSearch ??= string.Join(' ', SearchTerms[1..])) : "";
|
||||
|
||||
/// <summary>
|
||||
/// Return second search split by space if it has
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string SecondSearch => SplitSearch(1);
|
||||
|
||||
/// <summary>
|
||||
/// Return third search split by space if it has
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public string ThirdSearch => SplitSearch(2);
|
||||
|
||||
private string SplitSearch(int index)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
{
|
||||
[TestFixture]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
internal class JsonRPCPluginTest : JsonRPCPlugin
|
||||
internal class JsonRPCPluginTest : JsonRpcPlugin
|
||||
{
|
||||
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
|
||||
|
||||
|
|
@ -56,21 +56,14 @@ namespace Flow.Launcher.Test.Plugins
|
|||
|
||||
public static List<JsonRPCQueryResponseModel> ResponseModelsSource = new()
|
||||
{
|
||||
new()
|
||||
new JsonRPCQueryResponseModel(0, new List<JsonRPCResult>()),
|
||||
new JsonRPCQueryResponseModel(0, new List<JsonRPCResult>
|
||||
{
|
||||
Result = new()
|
||||
},
|
||||
new()
|
||||
{
|
||||
Result = new()
|
||||
new JsonRPCResult
|
||||
{
|
||||
new JsonRPCResult
|
||||
{
|
||||
Title = "Test1",
|
||||
SubTitle = "Test2"
|
||||
}
|
||||
Title = "Test1", SubTitle = "Test2"
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
[TestCaseSource(typeof(JsonRPCPluginTest), nameof(ResponseModelsSource))]
|
||||
|
|
@ -97,4 +90,4 @@ namespace Flow.Launcher.Test.Plugins
|
|||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue