Merge pull request #1498 from Flow-Launcher/add_nodejs_env

Add Node.js environment setup
This commit is contained in:
Jeremy Wu 2022-12-28 16:35:28 +09:30 committed by GitHub
commit 944e0f140f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 566 additions and 212 deletions

View file

@ -0,0 +1,234 @@
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
public abstract class AbstractPluginEnvironment
{
internal abstract string Language { get; }
internal abstract string EnvName { get; }
internal abstract string EnvPath { get; }
internal abstract string InstallPath { get; }
internal abstract string ExecutablePath { get; }
internal virtual string FileDialogFilter => string.Empty;
internal abstract string PluginsSettingsFilePath { get; set; }
internal List<PluginMetadata> PluginMetadataList;
internal PluginsSettings PluginSettings;
internal AbstractPluginEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings)
{
PluginMetadataList = pluginMetadataList;
PluginSettings = pluginSettings;
}
internal IEnumerable<PluginPair> Setup()
{
if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase)))
return new List<PluginPair>();
// TODO: Remove. This is backwards compatibility for 1.10.0 release- changed PythonEmbeded to Environments/Python
if (Language.Equals(AllowedLanguage.Python, StringComparison.OrdinalIgnoreCase))
{
FilesFolders.RemoveFolderIfExists(Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable"));
if (!string.IsNullOrEmpty(PluginSettings.PythonDirectory) && PluginSettings.PythonDirectory.StartsWith(Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable")))
{
InstallEnvironment();
PluginSettings.PythonDirectory = string.Empty;
}
}
if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath))
{
// Ensure latest only if user is using Flow's environment setup.
if (PluginsSettingsFilePath.StartsWith(EnvPath, StringComparison.OrdinalIgnoreCase))
EnsureLatestInstalled(ExecutablePath, PluginsSettingsFilePath, EnvPath);
return SetPathForPluginPairs(PluginsSettingsFilePath, Language);
}
if (MessageBox.Show($"Flow detected you have installed {Language} plugins, which " +
$"will require {EnvName} to run. Would you like to download {EnvName}? " +
Environment.NewLine + Environment.NewLine +
"Click no if it's already installed, " +
$"and you will be prompted to select the folder that contains the {EnvName} executable",
string.Empty, MessageBoxButtons.YesNo) == DialogResult.No)
{
var msg = $"Please select the {EnvName} executable";
var selectedFile = string.Empty;
selectedFile = GetFileFromDialog(msg, FileDialogFilter);
if (!string.IsNullOrEmpty(selectedFile))
PluginsSettingsFilePath = selectedFile;
// Nothing selected because user pressed cancel from the file dialog window
if (string.IsNullOrEmpty(selectedFile))
InstallEnvironment();
}
else
{
InstallEnvironment();
}
if (FilesFolders.FileExists(PluginsSettingsFilePath))
{
return SetPathForPluginPairs(PluginsSettingsFilePath, Language);
}
else
{
MessageBox.Show(
$"Unable to set {Language} executable path, please try from Flow's settings (scroll down to the bottom).");
Log.Error("PluginsLoader",
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
$"{Language}Environment");
return new List<PluginPair>();
}
}
internal abstract void InstallEnvironment();
private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath)
{
if (expectedPath == currentPath)
return;
FilesFolders.RemoveFolderIfExists(installedDirPath);
InstallEnvironment();
}
internal abstract PluginPair CreatePluginPair(string filePath, PluginMetadata metadata);
private IEnumerable<PluginPair> SetPathForPluginPairs(string filePath, string languageToSet)
{
var pluginPairs = new List<PluginPair>();
foreach (var metadata in PluginMetadataList)
{
if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase))
pluginPairs.Add(CreatePluginPair(filePath, metadata));
}
return pluginPairs;
}
private string GetFileFromDialog(string title, string filter = "")
{
var dlg = new OpenFileDialog
{
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
Multiselect = false,
CheckFileExists = true,
CheckPathExists = true,
Title = title,
Filter = filter
};
var result = dlg.ShowDialog();
if (result == DialogResult.OK)
{
return dlg.FileName;
}
else
{
return string.Empty;
}
}
/// <summary>
/// After app updated while in portable mode or switched between portable/roaming mode,
/// need to update each plugin's executable path so user will not be prompted again to reinstall the environments.
/// </summary>
/// <param name="settings"></param>
public static void PreStartPluginExecutablePathUpdate(Settings settings)
{
if (DataLocation.PortableDataLocationInUse())
{
// When user is using portable but has moved flow to a different location
if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName)
&& !settings.PluginSettings.PythonExecutablePath.StartsWith(DataLocation.PortableDataPath))
{
settings.PluginSettings.PythonExecutablePath
= GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath);
}
if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName)
&& !settings.PluginSettings.NodeExecutablePath.StartsWith(DataLocation.PortableDataPath))
{
settings.PluginSettings.NodeExecutablePath
= GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath);
}
// When user has switched from roaming to portable
if (IsUsingRoamingPath(settings.PluginSettings.PythonExecutablePath))
{
settings.PluginSettings.PythonExecutablePath
= settings.PluginSettings.PythonExecutablePath.Replace(DataLocation.RoamingDataPath, DataLocation.PortableDataPath);
}
if (IsUsingRoamingPath(settings.PluginSettings.NodeExecutablePath))
{
settings.PluginSettings.NodeExecutablePath
= settings.PluginSettings.NodeExecutablePath.Replace(DataLocation.RoamingDataPath, DataLocation.PortableDataPath);
}
}
else
{
if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName))
settings.PluginSettings.PythonExecutablePath
= GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath);
if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName))
settings.PluginSettings.NodeExecutablePath
= GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath);
}
}
private static bool IsUsingPortablePath(string filePath, string pluginEnvironmentName)
{
if (string.IsNullOrEmpty(filePath))
return false;
// DataLocation.PortableDataPath returns the current portable path, this determines if an out
// of date path is also a portable path.
var portableAppEnvLocation = $"UserData\\{DataLocation.PluginEnvironments}\\{pluginEnvironmentName}";
return filePath.Contains(portableAppEnvLocation);
}
private static bool IsUsingRoamingPath(string filePath)
{
if (string.IsNullOrEmpty(filePath))
return false;
return filePath.StartsWith(DataLocation.RoamingDataPath);
}
private static string GetUpdatedEnvironmentPath(string filePath)
{
var index = filePath.IndexOf(DataLocation.PluginEnvironments);
// get the substring after "Environments" because we can not determine it dynamically
var ExecutablePathSubstring = filePath.Substring(index + DataLocation.PluginEnvironments.Count());
return $"{DataLocation.PluginEnvironmentsPath}{ExecutablePathSubstring}";
}
}
}

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
internal class JavaScriptEnvironment : TypeScriptEnvironment
{
internal override string Language => AllowedLanguage.JavaScript;
internal JavaScriptEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
}
}

View file

@ -0,0 +1,48 @@
using Droplex;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using System.Collections.Generic;
using System.IO;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
internal class PythonEnvironment : AbstractPluginEnvironment
{
internal override string Language => AllowedLanguage.Python;
internal override string EnvName => DataLocation.PythonEnvironmentName;
internal override string EnvPath => Path.Combine(DataLocation.PluginEnvironmentsPath, EnvName);
internal override string InstallPath => Path.Combine(EnvPath, "PythonEmbeddable-v3.8.9");
internal override string ExecutablePath => Path.Combine(InstallPath, "pythonw.exe");
internal override string FileDialogFilter => "Python|pythonw.exe";
internal override string PluginsSettingsFilePath { get => PluginSettings.PythonExecutablePath; set => PluginSettings.PythonExecutablePath = value; }
internal PythonEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
internal override void InstallEnvironment()
{
FilesFolders.RemoveFolderIfExists(InstallPath);
// Python 3.8.9 is used for Windows 7 compatibility
DroplexPackage.Drop(App.python_3_8_9_embeddable, InstallPath).Wait();
PluginsSettingsFilePath = ExecutablePath;
}
internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata)
{
return new PluginPair
{
Plugin = new PythonPlugin(filePath),
Metadata = metadata
};
}
}
}

View file

@ -0,0 +1,44 @@
using System.Collections.Generic;
using Droplex;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin;
using System.IO;
using Flow.Launcher.Core.Plugin;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
internal class TypeScriptEnvironment : AbstractPluginEnvironment
{
internal override string Language => AllowedLanguage.TypeScript;
internal override string EnvName => DataLocation.NodeEnvironmentName;
internal override string EnvPath => Path.Combine(DataLocation.PluginEnvironmentsPath, EnvName);
internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0");
internal override string ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe");
internal override string PluginsSettingsFilePath { get => PluginSettings.NodeExecutablePath; set => PluginSettings.NodeExecutablePath = value; }
internal TypeScriptEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }
internal override void InstallEnvironment()
{
FilesFolders.RemoveFolderIfExists(InstallPath);
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();
PluginsSettingsFilePath = ExecutablePath;
}
internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata)
{
return new PluginPair
{
Plugin = new NodePlugin(filePath),
Metadata = metadata
};
}
}
}

View file

@ -53,7 +53,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Droplex" Version="1.4.1" />
<PackageReference Include="Droplex" Version="1.6.0" />
<PackageReference Include="FSharp.Core" Version="6.0.6" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.1" />
<PackageReference Include="squirrel.windows" Version="1.5.2" NoWarn="NU1701" />

View file

@ -9,7 +9,6 @@ namespace Flow.Launcher.Core.Plugin
internal class ExecutablePlugin : JsonRPCPlugin
{
private readonly ProcessStartInfo _startInfo;
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
public ExecutablePlugin(string filename)
{

View file

@ -37,10 +37,7 @@ namespace Flow.Launcher.Core.Plugin
{
protected PluginInitContext context;
public const string JsonRPC = "JsonRPC";
/// <summary>
/// The language this JsonRPCPlugin support
/// </summary>
public abstract string SupportedLanguage { get; set; }
protected abstract Task<Stream> RequestAsync(JsonRPCRequestModel rpcRequest, CancellationToken token = default);
protected abstract string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default);

View file

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Plugin
{
/// <summary>
/// Execution of JavaScript & TypeScript plugins
/// </summary>
internal class NodePlugin : JsonRPCPlugin
{
private readonly ProcessStartInfo _startInfo;
public NodePlugin(string filename)
{
_startInfo = new ProcessStartInfo
{
FileName = filename,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
}
protected override Task<Stream> RequestAsync(JsonRPCRequestModel request, CancellationToken token = default)
{
_startInfo.ArgumentList[1] = request.ToString();
return ExecuteAsync(_startInfo, token);
}
protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
{
// since this is not static, request strings will build up in ArgumentList if index is not specified
_startInfo.ArgumentList[1] = rpcRequest.ToString();
return Execute(_startInfo);
}
public override async Task InitAsync(PluginInitContext context)
{
_startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath);
_startInfo.ArgumentList.Add(string.Empty);
await base.InitAsync(context);
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
}
}
}

View file

@ -1,30 +1,38 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;
using Droplex;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Core.ExternalPlugins.Environments;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
namespace Flow.Launcher.Core.Plugin
{
public static class PluginsLoader
{
public const string PythonExecutable = "pythonw.exe";
public static List<PluginPair> Plugins(List<PluginMetadata> metadatas, PluginsSettings settings)
{
var dotnetPlugins = DotNetPlugins(metadatas);
var pythonPlugins = PythonPlugins(metadatas, settings);
var pythonEnv = new PythonEnvironment(metadatas, settings);
var tsEnv = new TypeScriptEnvironment(metadatas, settings);
var jsEnv = new JavaScriptEnvironment(metadatas, settings);
var pythonPlugins = pythonEnv.Setup();
var tsPlugins = tsEnv.Setup();
var jsPlugins = jsEnv.Setup();
var executablePlugins = ExecutablePlugins(metadatas);
var plugins = dotnetPlugins.Concat(pythonPlugins).Concat(executablePlugins).ToList();
var plugins = dotnetPlugins
.Concat(pythonPlugins)
.Concat(tsPlugins)
.Concat(jsPlugins)
.Concat(executablePlugins)
.ToList();
return plugins;
}
@ -108,97 +116,10 @@ namespace Flow.Launcher.Core.Plugin
return plugins;
}
public static IEnumerable<PluginPair> PythonPlugins(List<PluginMetadata> source, PluginsSettings settings)
{
if (!source.Any(o => o.Language.ToUpper() == AllowedLanguage.Python))
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 +
"Click no if it's already installed, " +
"and you will be prompted to select the folder that contains the Python executable",
string.Empty, MessageBoxButtons.YesNo) == DialogResult.No
&& string.IsNullOrEmpty(settings.PythonDirectory))
{
var dlg = new FolderBrowserDialog
{
SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
};
var result = dlg.ShowDialog();
if (result == DialogResult.OK)
{
string pythonDirectory = dlg.SelectedPath;
if (!string.IsNullOrEmpty(pythonDirectory))
{
pythonPath = Path.Combine(pythonDirectory, PythonExecutable);
if (File.Exists(pythonPath))
{
settings.PythonDirectory = pythonDirectory;
Constant.PythonPath = pythonPath;
}
else
{
MessageBox.Show("Can't find python in given directory");
}
}
}
}
else
{
var installedPythonDirectory = Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable");
// Python 3.8.9 is used for Windows 7 compatibility
DroplexPackage.Drop(App.python_3_8_9_embeddable, installedPythonDirectory).Wait();
pythonPath = Path.Combine(installedPythonDirectory, PythonExecutable);
if (FilesFolders.FileExists(pythonPath))
{
settings.PythonDirectory = installedPythonDirectory;
Constant.PythonPath = pythonPath;
}
else
{
Log.Error("PluginsLoader",
$"Failed to set Python path after Droplex install, {pythonPath} does not exist",
"PythonPlugins");
}
}
if (string.IsNullOrEmpty(settings.PythonDirectory) || string.IsNullOrEmpty(pythonPath))
{
MessageBox.Show(
"Unable to set Python executable path, please try from Flow's settings (scroll down to the bottom).");
Log.Error("PluginsLoader",
$"Not able to successfully set Python path, the PythonDirectory variable is still an empty string.",
"PythonPlugins");
return new List<PluginPair>();
}
return SetPythonPathForPluginPairs(source, pythonPath);
}
private static IEnumerable<PluginPair> SetPythonPathForPluginPairs(List<PluginMetadata> source, string pythonPath)
=> source
.Where(o => o.Language.ToUpper() == AllowedLanguage.Python)
.Select(metadata => new PluginPair
{
Plugin = new PythonPlugin(pythonPath),
Metadata = metadata
})
.ToList();
public static IEnumerable<PluginPair> ExecutablePlugins(IEnumerable<PluginMetadata> source)
{
return source
.Where(o => o.Language.ToUpper() == AllowedLanguage.Executable)
.Where(o => o.Language.Equals(AllowedLanguage.Executable, StringComparison.OrdinalIgnoreCase))
.Select(metadata => new PluginPair
{
Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), Metadata = metadata

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
@ -11,7 +11,6 @@ namespace Flow.Launcher.Core.Plugin
internal class PythonPlugin : JsonRPCPlugin
{
private readonly ProcessStartInfo _startInfo;
public override string SupportedLanguage { get; set; } = AllowedLanguage.Python;
public PythonPlugin(string filename)
{
@ -46,6 +45,7 @@ namespace Flow.Launcher.Core.Plugin
protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
{
// since this is not static, request strings will build up in ArgumentList if index is not specified
_startInfo.ArgumentList[2] = rpcRequest.ToString();
_startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory;
// TODO: Async Action

View file

@ -144,6 +144,5 @@ namespace Flow.Launcher.Core
return tips;
}
}
}

View file

@ -32,6 +32,7 @@ namespace Flow.Launcher.Infrastructure
public static readonly string LoadingImgIcon = Path.Combine(ImagesDirectory, "loading.png");
public static string PythonPath;
public static string NodePath;
public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.svg";

View file

@ -67,6 +67,7 @@ namespace Flow.Launcher.Infrastructure.Exception
sb.AppendLine($"* IntPtr Length: {IntPtr.Size}");
sb.AppendLine($"* x64: {Environment.Is64BitOperatingSystem}");
sb.AppendLine($"* Python Path: {Constant.PythonPath}");
sb.AppendLine($"* Node Path: {Constant.NodePath}");
sb.AppendLine($"* CLR Version: {Environment.Version}");
sb.AppendLine($"* Installed .NET Framework: ");
foreach (var result in GetFrameworkVersionFromRegistry())

View file

@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Flow.Launcher.Infrastructure.UserSettings
{
@ -31,5 +27,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins);
public static readonly string PluginSettingsDirectory = Path.Combine(DataDirectory(), "Settings", Constant.Plugins);
public const string PythonEnvironmentName = "Python";
public const string NodeEnvironmentName = "Node.js";
public const string PluginEnvironments = "Environments";
public static readonly string PluginEnvironmentsPath = Path.Combine(DataDirectory(), PluginEnvironments);
}
}

View file

@ -1,11 +1,34 @@
using System.Collections.Generic;
using System.Collections.Generic;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Infrastructure.UserSettings
{
public class PluginsSettings : BaseModel
{
private string pythonExecutablePath = string.Empty;
public string PythonExecutablePath {
get { return pythonExecutablePath; }
set
{
pythonExecutablePath = value;
Constant.PythonPath = value;
}
}
private string nodeExecutablePath = string.Empty;
public string NodeExecutablePath
{
get { return nodeExecutablePath; }
set
{
nodeExecutablePath = value;
Constant.NodePath = value;
}
}
// TODO: Remove. This is backwards compatibility for 1.10.0 release.
public string PythonDirectory { get; set; }
public Dictionary<string, Plugin> Plugins { get; set; } = new Dictionary<string, Plugin>();
public void UpdatePluginSettings(List<PluginMetadata> metadatas)

View file

@ -1,4 +1,6 @@
namespace Flow.Launcher.Plugin
using System;
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Allowed plugin languages
@ -8,34 +10,32 @@
/// <summary>
/// Python
/// </summary>
public static string Python
{
get { return "PYTHON"; }
}
public const string Python = "Python";
/// <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>
/// TypeScript
/// </summary>
public const string TypeScript = "TypeScript";
/// <summary>
/// JavaScript
/// </summary>
public const string JavaScript = "JavaScript";
/// <summary>
/// Determines if this language is a .NET language
@ -44,8 +44,8 @@
/// <returns></returns>
public static bool IsDotNet(string language)
{
return language.ToUpper() == CSharp
|| language.ToUpper() == FSharp;
return language.Equals(CSharp, StringComparison.OrdinalIgnoreCase)
|| language.Equals(FSharp, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
@ -56,8 +56,10 @@
public static bool IsAllowed(string language)
{
return IsDotNet(language)
|| language.ToUpper() == Python.ToUpper()
|| language.ToUpper() == Executable.ToUpper();
|| language.Equals(Python, StringComparison.OrdinalIgnoreCase)
|| language.Equals(Executable, StringComparison.OrdinalIgnoreCase)
|| language.Equals(TypeScript, StringComparison.OrdinalIgnoreCase)
|| language.Equals(JavaScript, StringComparison.OrdinalIgnoreCase);
}
}
}

View file

@ -1,4 +1,4 @@
using NUnit;
using NUnit;
using NUnit.Framework;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;
@ -16,8 +16,6 @@ namespace Flow.Launcher.Test.Plugins
// ReSharper disable once InconsistentNaming
internal class JsonRPCPluginTest : JsonRPCPlugin
{
public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable;
protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default)
{
throw new System.NotImplementedException();

View file

@ -6,6 +6,7 @@ using System.Timers;
using System.Windows;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Core.ExternalPlugins.Environments;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
@ -61,6 +62,8 @@ namespace Flow.Launcher
_settingsVM = new SettingWindowViewModel(_updater, _portable);
_settings = _settingsVM.Settings;
AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings);
_alphabet.Initialize(_settings);
_stringMatcher = new StringMatcher(_alphabet);
StringMatcher.Instance = _stringMatcher;

View file

@ -43,8 +43,8 @@ namespace Flow.Launcher.Helper
public static string DependenciesInfo()
{
var info = $"\nPython Path: {Constant.PythonPath}";
var info = $"\nPython Path: {Constant.PythonPath}\nNode Path: {Constant.NodePath}";
return info;
}
}
}
}

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Select the file manager to use when opening the folder.</system:String>
<system:String x:Key="defaultBrowser">Default Web Browser</system:String>
<system:String x:Key="defaultBrowserToolTip">Setting for New Tab, New Window, Private Mode.</system:String>
<system:String x:Key="pythonDirectory">Python bibliotek</system:String>
<system:String x:Key="autoUpdates">Autoopdatering</system:String>
<system:String x:Key="selectPythonDirectory">Vælg</system:String>
<system:String x:Key="select">Vælg</system:String>
<system:String x:Key="hideOnStartup">Skjul Flow Launcher ved opstart</system:String>
<system:String x:Key="hideNotifyIcon">Hide tray icon</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Wählen Sie den Dateimanager, der beim Öffnen des Ordners verwendet werden soll.</system:String>
<system:String x:Key="defaultBrowser">Standardbrowser</system:String>
<system:String x:Key="defaultBrowserToolTip">Einstellung für neuen Tab, neues Fenster und dem Privatmodus.</system:String>
<system:String x:Key="pythonDirectory">Python-Verzeichnis</system:String>
<system:String x:Key="autoUpdates">Automatische Aktualisierung</system:String>
<system:String x:Key="selectPythonDirectory">Auswählen</system:String>
<system:String x:Key="select">Auswählen</system:String>
<system:String x:Key="hideOnStartup">Verstecke Flow Launcher bei Systemstart</system:String>
<system:String x:Key="hideNotifyIcon">Statusleistensymbol ausblenden</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -57,11 +57,14 @@
<system:String x:Key="defaultFileManagerToolTip">Select the file manager to use when opening the folder.</system:String>
<system:String x:Key="defaultBrowser">Default Web Browser</system:String>
<system:String x:Key="defaultBrowserToolTip">Setting for New Tab, New Window, Private Mode.</system:String>
<system:String x:Key="pythonFilePath">Python Path</system:String>
<system:String x:Key="nodeFilePath">Node.js Path</system:String>
<system:String x:Key="selectNodeExecutable">Please select the Node.js executable</system:String>
<system:String x:Key="selectPythonExecutable">Please select pythonw.exe</system:String>
<system:String x:Key="typingStartEn">Always Start Typing in English Mode</system:String>
<system:String x:Key="typingStartEnTooltip">Temporarily change your input method to English mode when activating Flow.</system:String>
<system:String x:Key="pythonDirectory">Python Directory</system:String>
<system:String x:Key="autoUpdates">Auto Update</system:String>
<system:String x:Key="selectPythonDirectory">Select</system:String>
<system:String x:Key="select">Select</system:String>
<system:String x:Key="hideOnStartup">Hide Flow Launcher on startup</system:String>
<system:String x:Key="hideNotifyIcon">Hide tray icon</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>
@ -293,7 +296,7 @@
<!-- General Notice -->
<system:String x:Key="pleaseWait">Please wait...</system:String>
<!-- update -->
<!-- Update -->
<system:String x:Key="update_flowlauncher_update_check">Checking for new update</system:String>
<system:String x:Key="update_flowlauncher_already_on_latest">You already have the latest Flow Launcher version</system:String>
<system:String x:Key="update_flowlauncher_update_found">Update found</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Seleccione el gestor de archivos a utilizar al abrir la carpeta.</system:String>
<system:String x:Key="defaultBrowser">Navegador web predeterminado</system:String>
<system:String x:Key="defaultBrowserToolTip">Configuración para Nueva Pestaña, Nueva Ventana, Modo Privado.</system:String>
<system:String x:Key="pythonDirectory">Directorio de Python</system:String>
<system:String x:Key="autoUpdates">Actualización automática</system:String>
<system:String x:Key="selectPythonDirectory">Seleccionar</system:String>
<system:String x:Key="select">Seleccionar</system:String>
<system:String x:Key="hideOnStartup">Ocultar Flow Launcher al arrancar el sistema</system:String>
<system:String x:Key="hideNotifyIcon">Ocultar icono de la bandeja</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Selecciona el administrador de archivos que se desea utilizar para abrir la carpeta.</system:String>
<system:String x:Key="defaultBrowser">Navegador web predeterminado</system:String>
<system:String x:Key="defaultBrowserToolTip">Configuración para Nueva Pestaña, Nueva Ventana, Modo Privado.</system:String>
<system:String x:Key="pythonDirectory">Carpeta de Python</system:String>
<system:String x:Key="autoUpdates">Actualización automática</system:String>
<system:String x:Key="selectPythonDirectory">Seleccionar</system:String>
<system:String x:Key="select">Seleccionar</system:String>
<system:String x:Key="hideOnStartup">Ocultar Flow Launcher al inicio</system:String>
<system:String x:Key="hideNotifyIcon">Ocultar icono de la bandeja del sistema</system:String>
<system:String x:Key="hideNotifyIconToolTip">Cuando el icono está oculto en la bandeja del sistema, se puede abrir el menú de configuración haciendo clic con el botón derecho en la ventana de búsqueda.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Select the file manager to use when opening the folder.</system:String>
<system:String x:Key="defaultBrowser">Navigateur web par défaut</system:String>
<system:String x:Key="defaultBrowserToolTip">Setting for New Tab, New Window, Private Mode.</system:String>
<system:String x:Key="pythonDirectory">Répertoire de Python</system:String>
<system:String x:Key="autoUpdates">Mettre à jour automatiquement</system:String>
<system:String x:Key="selectPythonDirectory">Sélectionner</system:String>
<system:String x:Key="select">Sélectionner</system:String>
<system:String x:Key="hideOnStartup">Cacher Flow Launcher au démarrage</system:String>
<system:String x:Key="hideNotifyIcon">Masquer icône du plateau</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Selezionare il Gestore file da usare all'apertura della cartella.</system:String>
<system:String x:Key="defaultBrowser">Browser predefinito</system:String>
<system:String x:Key="defaultBrowserToolTip">Impostazione per Nuova scheda, Nuova finestra, Modalità privata.</system:String>
<system:String x:Key="pythonDirectory">Cartella Python</system:String>
<system:String x:Key="autoUpdates">Aggiornamento automatico</system:String>
<system:String x:Key="selectPythonDirectory">Seleziona</system:String>
<system:String x:Key="select">Seleziona</system:String>
<system:String x:Key="hideOnStartup">Nascondi Flow Launcher all'avvio</system:String>
<system:String x:Key="hideNotifyIcon">Nascondi Icona nell'Area di Notifica</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Select the file manager to use when opening the folder.</system:String>
<system:String x:Key="defaultBrowser">Default Web Browser</system:String>
<system:String x:Key="defaultBrowserToolTip">Setting for New Tab, New Window, Private Mode.</system:String>
<system:String x:Key="pythonDirectory">Pythonのディレクトリ</system:String>
<system:String x:Key="autoUpdates">自動更新</system:String>
<system:String x:Key="selectPythonDirectory">選択</system:String>
<system:String x:Key="select">選択</system:String>
<system:String x:Key="hideOnStartup">起動時にFlow Launcherを隠す</system:String>
<system:String x:Key="hideNotifyIcon">トレイアイコンを隠す</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">폴더를 열 때 사용할 파일관리자를 선택하세요.</system:String>
<system:String x:Key="defaultBrowser">기본 웹 브라우저</system:String>
<system:String x:Key="defaultBrowserToolTip">새 탭, 새 창, 사생활 보호 모드</system:String>
<system:String x:Key="pythonDirectory">Python 디렉토리</system:String>
<system:String x:Key="autoUpdates">자동 업데이트</system:String>
<system:String x:Key="selectPythonDirectory">선택</system:String>
<system:String x:Key="select">선택</system:String>
<system:String x:Key="hideOnStartup">시작 시 Flow Launcher 숨김</system:String>
<system:String x:Key="hideNotifyIcon">트레이 아이콘 숨기기</system:String>
<system:String x:Key="hideNotifyIconToolTip">트레이에서 아이콘을 숨길 경우, 검색창 우클릭으로 설정창을 열 수 있습니다.</system:String>

View file

@ -28,9 +28,8 @@
<system:String x:Key="LastQueryEmpty">Tøm siste spørring</system:String>
<system:String x:Key="maxShowResults">Maks antall resultater vist</system:String>
<system:String x:Key="ignoreHotkeysOnFullscreen">Ignorer hurtigtaster i fullskjermsmodus</system:String>
<system:String x:Key="pythonDirectory">Python-mappe</system:String>
<system:String x:Key="autoUpdates">Oppdater automatisk</system:String>
<system:String x:Key="selectPythonDirectory">Velg</system:String>
<system:String x:Key="select">Velg</system:String>
<system:String x:Key="hideOnStartup">Skjul Flow Launcher ved oppstart</system:String>
<!--Setting Plugin-->

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Select the file manager to use when opening the folder.</system:String>
<system:String x:Key="defaultBrowser">Default Web Browser</system:String>
<system:String x:Key="defaultBrowserToolTip">Setting for New Tab, New Window, Private Mode.</system:String>
<system:String x:Key="pythonDirectory">Python Directory</system:String>
<system:String x:Key="autoUpdates">Auto Update</system:String>
<system:String x:Key="selectPythonDirectory">Select</system:String>
<system:String x:Key="select">Select</system:String>
<system:String x:Key="hideOnStartup">Hide Flow Launcher on startup</system:String>
<system:String x:Key="hideNotifyIcon">Hide tray icon</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Selecteer de bestandsbeheerder voor het openen van de map.</system:String>
<system:String x:Key="defaultBrowser">Standaard webbrowser</system:String>
<system:String x:Key="defaultBrowserToolTip">Instelling voor Nieuw tabblad, Nieuw Venster, Privémodus.</system:String>
<system:String x:Key="pythonDirectory">Python map</system:String>
<system:String x:Key="autoUpdates">Automatische Update</system:String>
<system:String x:Key="selectPythonDirectory">Selecteer</system:String>
<system:String x:Key="select">Selecteer</system:String>
<system:String x:Key="hideOnStartup">Verberg Flow Launcher als systeem opstart</system:String>
<system:String x:Key="hideNotifyIcon">Systeemvakpictogram verbergen</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Wybierz menedżer plików używany do otwierania folderów.</system:String>
<system:String x:Key="defaultBrowser">Domyślna przeglądarka</system:String>
<system:String x:Key="defaultBrowserToolTip">Ustawienie dla nowej karty, nowego okna i trybu prywatnego.</system:String>
<system:String x:Key="pythonDirectory">Folder biblioteki Python</system:String>
<system:String x:Key="autoUpdates">Automatyczne aktualizacje</system:String>
<system:String x:Key="selectPythonDirectory">Wybierz</system:String>
<system:String x:Key="select">Wybierz</system:String>
<system:String x:Key="hideOnStartup">Uruchamiaj Flow Launcher zminimalizowany</system:String>
<system:String x:Key="hideNotifyIcon">Ukryj ikonę zasobnika</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Select the file manager to use when opening the folder.</system:String>
<system:String x:Key="defaultBrowser">Default Web Browser</system:String>
<system:String x:Key="defaultBrowserToolTip">Setting for New Tab, New Window, Private Mode.</system:String>
<system:String x:Key="pythonDirectory">Diretório Python</system:String>
<system:String x:Key="autoUpdates">Atualizar Automaticamente</system:String>
<system:String x:Key="selectPythonDirectory">Selecionar</system:String>
<system:String x:Key="select">Selecionar</system:String>
<system:String x:Key="hideOnStartup">Esconder Flow Launcher na inicialização</system:String>
<system:String x:Key="hideNotifyIcon">Hide tray icon</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Selecione o gestor de ficheiros utilizado para abrir a página</system:String>
<system:String x:Key="defaultBrowser">Navegador web padrão</system:String>
<system:String x:Key="defaultBrowserToolTip">Definições para Novo separador, Nova Janela e Modo privado</system:String>
<system:String x:Key="pythonDirectory">Diretório Python</system:String>
<system:String x:Key="autoUpdates">Atualização automática</system:String>
<system:String x:Key="selectPythonDirectory">Selecionar</system:String>
<system:String x:Key="select">Selecionar</system:String>
<system:String x:Key="hideOnStartup">Ocultar Flow Launcher ao arrancar</system:String>
<system:String x:Key="hideNotifyIcon">Ocultar ícone na bandeja</system:String>
<system:String x:Key="hideNotifyIconToolTip">Se o ícone da bandeja estiver oculto, pode abrir as Definições com um clique com o botão direito do rato na caixa de pesquisa.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Select the file manager to use when opening the folder.</system:String>
<system:String x:Key="defaultBrowser">Default Web Browser</system:String>
<system:String x:Key="defaultBrowserToolTip">Setting for New Tab, New Window, Private Mode.</system:String>
<system:String x:Key="pythonDirectory">Python Directory</system:String>
<system:String x:Key="autoUpdates">Auto Update</system:String>
<system:String x:Key="selectPythonDirectory">Select</system:String>
<system:String x:Key="select">Select</system:String>
<system:String x:Key="hideOnStartup">Hide Flow Launcher on startup</system:String>
<system:String x:Key="hideNotifyIcon">Hide tray icon</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Vyberte správcu súborov, ktorý sa má použiť pri otváraní priečinka.</system:String>
<system:String x:Key="defaultBrowser">Predvolený webový prehliadač</system:String>
<system:String x:Key="defaultBrowserToolTip">Nastavenie pre novú kartu, nové okno, privátny režim.</system:String>
<system:String x:Key="pythonDirectory">Priečinok s Pythonom</system:String>
<system:String x:Key="autoUpdates">Automatická aktualizácia</system:String>
<system:String x:Key="selectPythonDirectory">Vybrať</system:String>
<system:String x:Key="select">Vybrať</system:String>
<system:String x:Key="hideOnStartup">Schovať Flow Launcher po spustení</system:String>
<system:String x:Key="hideNotifyIcon">Schovať ikonu z oblasti oznámení</system:String>
<system:String x:Key="hideNotifyIconToolTip">Keď je ikona skrytá z oblasti oznámení, nastavenia možno otvoriť kliknutím pravým tlačidlom myši na okno vyhľadávania.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Select the file manager to use when opening the folder.</system:String>
<system:String x:Key="defaultBrowser">Default Web Browser</system:String>
<system:String x:Key="defaultBrowserToolTip">Setting for New Tab, New Window, Private Mode.</system:String>
<system:String x:Key="pythonDirectory">Python direktorijum</system:String>
<system:String x:Key="autoUpdates">Auto ažuriranje</system:String>
<system:String x:Key="selectPythonDirectory">Izaberi</system:String>
<system:String x:Key="select">Izaberi</system:String>
<system:String x:Key="hideOnStartup">Sakrij Flow Launcher pri podizanju sistema</system:String>
<system:String x:Key="hideNotifyIcon">Hide tray icon</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Klasör açarken kullanılacak dosya yöneticisini seçin.</system:String>
<system:String x:Key="defaultBrowser">Varsayılan Tarayıcısı</system:String>
<system:String x:Key="defaultBrowserToolTip">Yeni Sekme, Yeni Pencere, Gizli Mod için Ayar.</system:String>
<system:String x:Key="pythonDirectory">Python Konumu</system:String>
<system:String x:Key="autoUpdates">Otomatik Güncelle</system:String>
<system:String x:Key="selectPythonDirectory">Seç</system:String>
<system:String x:Key="select">Seç</system:String>
<system:String x:Key="hideOnStartup">Başlangıçta Flow Launcher'u gizle</system:String>
<system:String x:Key="hideNotifyIcon">Sistem çekmecesi simgesini gizle</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">Виберіть файловий менеджер для використання під час відкриття теки.</system:String>
<system:String x:Key="defaultBrowser">Браузер за замовчуванням</system:String>
<system:String x:Key="defaultBrowserToolTip">Налаштування нової вкладки, нового вікна, приватного режиму.</system:String>
<system:String x:Key="pythonDirectory">Директорія Python</system:String>
<system:String x:Key="autoUpdates">Автоматичне оновлення</system:String>
<system:String x:Key="selectPythonDirectory">Вибрати</system:String>
<system:String x:Key="select">Вибрати</system:String>
<system:String x:Key="hideOnStartup">Сховати Flow Launcher при запуску системи</system:String>
<system:String x:Key="hideNotifyIcon">Приховати значок в системному лотку</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">选择打开文件夹时要使用的文件管理器。</system:String>
<system:String x:Key="defaultBrowser">默认浏览器</system:String>
<system:String x:Key="defaultBrowserToolTip">新标签/窗口及隐身模式设置。</system:String>
<system:String x:Key="pythonDirectory">Python 路径</system:String>
<system:String x:Key="autoUpdates">自动更新</system:String>
<system:String x:Key="selectPythonDirectory">选择</system:String>
<system:String x:Key="select">选择</system:String>
<system:String x:Key="hideOnStartup">系统启动时不显示主窗口</system:String>
<system:String x:Key="hideNotifyIcon">隐藏任务栏图标</system:String>
<system:String x:Key="hideNotifyIconToolTip">任务栏图标被隐藏时,右键点击搜索窗口即可打开设置菜单。</system:String>

View file

@ -53,9 +53,8 @@
<system:String x:Key="defaultFileManagerToolTip">選擇開啟資料夾時要使用的檔案管理器。</system:String>
<system:String x:Key="defaultBrowser">預設瀏覽器</system:String>
<system:String x:Key="defaultBrowserToolTip">設定新增分頁、視窗和無痕模式。</system:String>
<system:String x:Key="pythonDirectory">Python 路徑</system:String>
<system:String x:Key="autoUpdates">自動更新</system:String>
<system:String x:Key="selectPythonDirectory">選擇</system:String>
<system:String x:Key="select">選擇</system:String>
<system:String x:Key="hideOnStartup">啟動時不顯示主視窗</system:String>
<system:String x:Key="hideNotifyIcon">隱藏任務欄圖標</system:String>
<system:String x:Key="hideNotifyIconToolTip">When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window.</system:String>

View file

@ -166,7 +166,7 @@
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="btnBrowseFile_Click"
Content="{DynamicResource selectPythonDirectory}"
Content="{DynamicResource select}"
DockPanel.Dock="Right">
<Button.Style>
<Style BasedOn="{StaticResource DefaultButtonStyle}" TargetType="{x:Type Button}">

View file

@ -164,7 +164,7 @@
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="btnBrowseFile_Click"
Content="{DynamicResource selectPythonDirectory}"
Content="{DynamicResource select}"
DockPanel.Dock="Right">
<Button.Style>
<Style BasedOn="{StaticResource DefaultButtonStyle}" TargetType="{x:Type Button}">

View file

@ -919,18 +919,37 @@
<Border Margin="0,30,0,0" Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource pythonDirectory}" />
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource pythonFilePath}" />
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<TextBox
Width="300"
Height="34"
Text="{Binding Settings.PluginSettings.PythonDirectory, TargetNullValue='No Setting'}" />
Text="{Binding Settings.PluginSettings.PythonExecutablePath, TargetNullValue='None'}" />
<Button
Height="34"
Margin="10,0,18,0"
Click="OnSelectPythonDirectoryClick"
Content="{DynamicResource selectPythonDirectory}" />
Click="OnSelectPythonPathClick"
Content="{DynamicResource select}" />
</StackPanel>
</ItemsControl>
</Border>
<Border Margin="0,4,0,0" Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource nodeFilePath}" />
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<TextBox
Width="300"
Height="34"
Text="{Binding Settings.PluginSettings.NodeExecutablePath, TargetNullValue='None'}" />
<Button
Height="34"
Margin="10,0,18,0"
Click="OnSelectNodePathClick"
Content="{DynamicResource select}" />
</StackPanel>
</ItemsControl>
</Border>
@ -974,7 +993,7 @@
</ItemsControl>
</Border>
<Border Style="{DynamicResource SettingGroupBox}">
<Border Margin="0,30,0,0" Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource language}" />

View file

@ -64,31 +64,23 @@ namespace Flow.Launcher
InitializePosition();
}
private void OnSelectPythonDirectoryClick(object sender, RoutedEventArgs e)
private void OnSelectPythonPathClick(object sender, RoutedEventArgs e)
{
var dlg = new FolderBrowserDialog
{
SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
};
var selectedFile = viewModel.GetFileFromDialog(
InternationalizationManager.Instance.GetTranslation("selectPythonExecutable"),
"Python|pythonw.exe");
var result = dlg.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK)
{
string pythonDirectory = dlg.SelectedPath;
if (!string.IsNullOrEmpty(pythonDirectory))
{
var pythonPath = Path.Combine(pythonDirectory, PluginsLoader.PythonExecutable);
if (File.Exists(pythonPath))
{
settings.PluginSettings.PythonDirectory = pythonDirectory;
MessageBox.Show("Remember to restart Flow Launcher use new Python path");
}
else
{
MessageBox.Show("Can't find python in given directory");
}
}
}
if (!string.IsNullOrEmpty(selectedFile))
settings.PluginSettings.PythonExecutablePath = selectedFile;
}
private void OnSelectNodePathClick(object sender, RoutedEventArgs e)
{
var selectedFile = viewModel.GetFileFromDialog(
InternationalizationManager.Instance.GetTranslation("selectNodeExecutable"));
if (!string.IsNullOrEmpty(selectedFile))
settings.PluginSettings.NodeExecutablePath = selectedFile;
}
private void OnSelectFileManagerClick(object sender, RoutedEventArgs e)

View file

@ -143,6 +143,29 @@ namespace Flow.Launcher.ViewModel
_storage.Save();
}
public string GetFileFromDialog(string title, string filter = "")
{
var dlg = new System.Windows.Forms.OpenFileDialog
{
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
Multiselect = false,
CheckFileExists = true,
CheckPathExists = true,
Title = title,
Filter = filter
};
var result = dlg.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK)
{
return dlg.FileName;
}
else
{
return string.Empty;
}
}
#region general
// todo a better name?