Merge branch 'dev' of github.com:Flow-Launcher/Flow.Launcher into net7.0

This commit is contained in:
Hongtao Zhang 2023-02-04 11:52:15 -06:00
commit 704c26ca3d
No known key found for this signature in database
GPG key ID: 75F655B91C7AC9BB
61 changed files with 1072 additions and 531 deletions

View file

@ -62,3 +62,29 @@ TobiasSekan
Img
img
resx
bak
tmp
directx
mvvm
dlg
ddd
dddd
clearlogfolder
ACCENT_ENABLE_TRANSPARENTGRADIENT
ACCENT_ENABLE_BLURBEHIND
WCA_ACCENT_POLICY
HGlobal
dopusrt
firefox
msedge
svgc
ime
zindex
txb
btn
otf
searchplugin
Noresult
wpftk
mkv
flac

View file

@ -259,9 +259,16 @@ namespace Flow.Launcher.Core.Plugin
await using var registeredEvent = token.Register(() =>
{
if (!process.HasExited)
process.Kill();
sourceBuffer.Dispose();
try
{
if (!process.HasExited)
process.Kill();
sourceBuffer.Dispose();
}
catch (Exception e)
{
Log.Exception("|JsonRPCPlugin.ExecuteAsync|Exception when kill process", e);
}
});
try
@ -288,7 +295,7 @@ namespace Flow.Launcher.Core.Plugin
}
sourceBuffer.Seek(0, SeekOrigin.Begin);
return sourceBuffer;
}
@ -376,7 +383,8 @@ namespace Flow.Launcher.Core.Plugin
sep.SetResourceReference(Separator.BackgroundProperty, "Color03B"); /* for theme change */
var panel = new StackPanel
{
Orientation = Orientation.Vertical, VerticalAlignment = VerticalAlignment.Center,
Orientation = Orientation.Vertical,
VerticalAlignment = VerticalAlignment.Center,
Margin = settingLabelPanelMargin
};
RowDefinition gridRow = new RowDefinition();
@ -390,8 +398,10 @@ namespace Flow.Launcher.Core.Plugin
};
var desc = new TextBlock()
{
Text = attribute.Description, FontSize = 12,
VerticalAlignment = VerticalAlignment.Center,Margin = settingDescMargin,
Text = attribute.Description,
FontSize = 12,
VerticalAlignment = VerticalAlignment.Center,
Margin = settingDescMargin,
TextWrapping = TextWrapping.WrapWithOverflow
};
desc.SetResourceReference(TextBlock.ForegroundProperty, "Color04B");
@ -405,7 +415,7 @@ namespace Flow.Launcher.Core.Plugin
panel.Children.Add(name);
panel.Children.Add(desc);
}
Grid.SetColumn(panel, 0);
Grid.SetRow(panel, rowCount);
@ -420,20 +430,20 @@ namespace Flow.Launcher.Core.Plugin
{
Text = attribute.Description.Replace("\\r\\n", "\r\n"),
Margin = settingTextBlockMargin,
Padding = new Thickness(0,0,0,0),
Padding = new Thickness(0, 0, 0, 0),
HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
TextAlignment = TextAlignment.Left,
TextWrapping = TextWrapping.Wrap
};
Grid.SetColumn(contentControl, 0);
Grid.SetColumnSpan(contentControl, 2);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
Grid.SetColumn(contentControl, 0);
Grid.SetColumnSpan(contentControl, 2);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
}
case "input":
{
@ -449,50 +459,49 @@ namespace Flow.Launcher.Core.Plugin
Settings[attribute.Name] = textBox.Text;
};
contentControl = textBox;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
}
case "inputWithFileBtn":
{
var textBox = new TextBox()
{
var textBox = new TextBox()
{
Margin = new Thickness(10, 0, 0, 0),
Text = Settings[attribute.Name] as string ?? string.Empty,
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
ToolTip = attribute.Description
};
textBox.TextChanged += (_, _) =>
{
Settings[attribute.Name] = textBox.Text;
};
var Btn = new System.Windows.Controls.Button()
{
Margin = new Thickness(10,0,0,0),
Content = "Browse"
};
var dockPanel = new DockPanel()
{
Margin = settingControlMargin
};
DockPanel.SetDock(Btn, Dock.Right);
dockPanel.Children.Add(Btn);
dockPanel.Children.Add(textBox);
contentControl = dockPanel;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
}
Margin = new Thickness(10, 0, 0, 0),
Text = Settings[attribute.Name] as string ?? string.Empty,
HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
ToolTip = attribute.Description
};
textBox.TextChanged += (_, _) =>
{
Settings[attribute.Name] = textBox.Text;
};
var Btn = new System.Windows.Controls.Button()
{
Margin = new Thickness(10, 0, 0, 0), Content = "Browse"
};
var dockPanel = new DockPanel()
{
Margin = settingControlMargin
};
DockPanel.SetDock(Btn, Dock.Right);
dockPanel.Children.Add(Btn);
dockPanel.Children.Add(textBox);
contentControl = dockPanel;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
}
case "textarea":
{
var textBox = new TextBox()
@ -511,14 +520,14 @@ namespace Flow.Launcher.Core.Plugin
Settings[attribute.Name] = ((TextBox)sender).Text;
};
contentControl = textBox;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
}
case "passwordBox":
{
@ -535,14 +544,14 @@ namespace Flow.Launcher.Core.Plugin
Settings[attribute.Name] = ((PasswordBox)sender).Password;
};
contentControl = passwordBox;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
}
case "dropdown":
{
@ -559,14 +568,14 @@ namespace Flow.Launcher.Core.Plugin
Settings[attribute.Name] = (string)((System.Windows.Controls.ComboBox)sender).SelectedItem;
};
contentControl = comboBox;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
Grid.SetColumn(contentControl, 1);
Grid.SetRow(contentControl, rowCount);
if (rowCount != 0)
mainPanel.Children.Add(sep);
Grid.SetRow(sep, rowCount);
Grid.SetColumn(sep, 0);
Grid.SetColumnSpan(sep, 2);
break;
}
case "checkbox":
var checkBox = new CheckBox
@ -592,13 +601,11 @@ namespace Flow.Launcher.Core.Plugin
case "hyperlink":
var hyperlink = new Hyperlink
{
ToolTip = attribute.Description,
NavigateUri = attribute.url
ToolTip = attribute.Description, NavigateUri = attribute.url
};
var linkbtn = new System.Windows.Controls.Button
{
HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
Margin = settingControlMargin
HorizontalAlignment = System.Windows.HorizontalAlignment.Right, Margin = settingControlMargin
};
linkbtn.Content = attribute.urlLabel;
@ -619,7 +626,7 @@ namespace Flow.Launcher.Core.Plugin
mainPanel.Children.Add(panel);
mainPanel.Children.Add(contentControl);
rowCount++;
}
return settingWindow;
}

View file

@ -36,7 +36,7 @@ namespace Flow.Launcher.Core.Resource
{
_themeDirectories.Add(DirectoryPath);
_themeDirectories.Add(UserDirectoryPath);
MakesureThemeDirectoriesExist();
MakeSureThemeDirectoriesExist();
var dicts = Application.Current.Resources.MergedDictionaries;
_oldResource = dicts.First(d =>
@ -55,20 +55,17 @@ namespace Flow.Launcher.Core.Resource
_oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath);
}
private void MakesureThemeDirectoriesExist()
private void MakeSureThemeDirectoriesExist()
{
foreach (string dir in _themeDirectories)
foreach (var dir in _themeDirectories.Where(dir => !Directory.Exists(dir)))
{
if (!Directory.Exists(dir))
try
{
try
{
Directory.CreateDirectory(dir);
}
catch (Exception e)
{
Log.Exception($"|Theme.MakesureThemeDirectoriesExist|Exception when create directory <{dir}>", e);
}
Directory.CreateDirectory(dir);
}
catch (Exception e)
{
Log.Exception($"|Theme.MakesureThemeDirectoriesExist|Exception when create directory <{dir}>", e);
}
}
}
@ -82,13 +79,14 @@ namespace Flow.Launcher.Core.Resource
{
if (string.IsNullOrEmpty(path))
throw new DirectoryNotFoundException("Theme path can't be found <{path}>");
Settings.Theme = theme;
// reload all resources even if the theme itself hasn't changed in order to pickup changes
// to things like fonts
UpdateResourceDictionary(GetResourceDictionary());
UpdateResourceDictionary(GetResourceDictionary(theme));
Settings.Theme = theme;
//always allow re-loading default theme, in case of failure of switching to a new theme from default theme
if (_oldTheme != theme || theme == defaultTheme)
{
@ -134,9 +132,9 @@ namespace Flow.Launcher.Core.Resource
_oldResource = dictionaryToUpdate;
}
private ResourceDictionary CurrentThemeResourceDictionary()
private ResourceDictionary GetThemeResourceDictionary(string theme)
{
var uri = GetThemePath(Settings.Theme);
var uri = GetThemePath(theme);
var dict = new ResourceDictionary
{
Source = new Uri(uri, UriKind.Absolute)
@ -145,10 +143,12 @@ namespace Flow.Launcher.Core.Resource
return dict;
}
public ResourceDictionary GetResourceDictionary()
private ResourceDictionary CurrentThemeResourceDictionary() => GetThemeResourceDictionary(Settings.Theme);
public ResourceDictionary GetResourceDictionary(string theme)
{
var dict = CurrentThemeResourceDictionary();
var dict = GetThemeResourceDictionary(theme);
if (dict["QueryBoxStyle"] is Style queryBoxStyle &&
dict["QuerySuggestionBoxStyle"] is Style querySuggestionBoxStyle)
{
@ -200,6 +200,11 @@ namespace Flow.Launcher.Core.Resource
return dict;
}
private ResourceDictionary GetCurrentResourceDictionary( )
{
return GetResourceDictionary(Settings.Theme);
}
public List<string> LoadAvailableThemes()
{
List<string> themes = new List<string>();
@ -229,7 +234,7 @@ namespace Flow.Launcher.Core.Resource
public void AddDropShadowEffectToCurrentTheme()
{
var dict = GetResourceDictionary();
var dict = GetCurrentResourceDictionary();
var windowBorderStyle = dict["WindowBorderStyle"] as Style;
@ -273,7 +278,7 @@ namespace Flow.Launcher.Core.Resource
public void RemoveDropShadowEffectFromCurrentTheme()
{
var dict = CurrentThemeResourceDictionary();
var dict = GetCurrentResourceDictionary();
var windowBorderStyle = dict["WindowBorderStyle"] as Style;
var effectSetter = windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.EffectProperty) as Setter;

View file

@ -33,7 +33,7 @@ namespace Flow.Launcher.Core
public async Task UpdateAppAsync(IPublicAPI api, bool silentUpdate = true)
{
await UpdateLock.WaitAsync();
await UpdateLock.WaitAsync().ConfigureAwait(false);
try
{
if (!silentUpdate)
@ -88,9 +88,13 @@ namespace Flow.Launcher.Core
UpdateManager.RestartApp(Constant.ApplicationFileName);
}
}
catch (Exception e) when (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
catch (Exception e)
{
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
if ((e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException))
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
else
Log.Exception($"|Updater.UpdateApp|Error Occurred", e);
if (!silentUpdate)
api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"),
api.GetTranslation("update_flowlauncher_check_connection"));

View file

@ -1,4 +1,6 @@
using System;
#nullable enable
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.Json;
@ -16,7 +18,7 @@ namespace Flow.Launcher.Infrastructure
/// <summary>
/// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy
/// </summary>
public static T NonNull<T>(this T obj)
public static T NonNull<T>(this T? obj)
{
if (obj == null)
{

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using System.Windows.Navigation;
namespace Flow.Launcher.Infrastructure.Hotkey
{
@ -11,10 +12,10 @@ namespace Flow.Launcher.Infrastructure.Hotkey
public bool Shift { get; set; }
public bool Win { get; set; }
public bool Ctrl { get; set; }
public Key CharKey { get; set; }
public Key CharKey { get; set; } = Key.None;
Dictionary<Key, string> specialSymbolDictionary = new Dictionary<Key, string>
private static readonly Dictionary<Key, string> specialSymbolDictionary = new Dictionary<Key, string>
{
{Key.Space, "Space"},
{Key.Oem3, "~"}
@ -27,19 +28,19 @@ namespace Flow.Launcher.Infrastructure.Hotkey
ModifierKeys modifierKeys = ModifierKeys.None;
if (Alt)
{
modifierKeys = ModifierKeys.Alt;
modifierKeys |= ModifierKeys.Alt;
}
if (Shift)
{
modifierKeys = modifierKeys | ModifierKeys.Shift;
modifierKeys |= ModifierKeys.Shift;
}
if (Win)
{
modifierKeys = modifierKeys | ModifierKeys.Windows;
modifierKeys |= ModifierKeys.Windows;
}
if (Ctrl)
{
modifierKeys = modifierKeys | ModifierKeys.Control;
modifierKeys |= ModifierKeys.Control;
}
return modifierKeys;
}
@ -86,7 +87,7 @@ namespace Flow.Launcher.Infrastructure.Hotkey
Ctrl = true;
keys.Remove("Ctrl");
}
if (keys.Count > 0)
if (keys.Count == 1)
{
string charKey = keys[0];
KeyValuePair<Key, string>? specialSymbolPair = specialSymbolDictionary.FirstOrDefault(pair => pair.Value == charKey);
@ -110,36 +111,75 @@ namespace Flow.Launcher.Infrastructure.Hotkey
public override string ToString()
{
string text = string.Empty;
List<string> keys = new List<string>();
if (Ctrl)
{
text += "Ctrl + ";
keys.Add("Ctrl");
}
if (Alt)
{
text += "Alt + ";
keys.Add("Alt");
}
if (Shift)
{
text += "Shift + ";
keys.Add("Shift");
}
if (Win)
{
text += "Win + ";
keys.Add("Win");
}
if (CharKey != Key.None)
{
text += specialSymbolDictionary.ContainsKey(CharKey)
keys.Add(specialSymbolDictionary.ContainsKey(CharKey)
? specialSymbolDictionary[CharKey]
: CharKey.ToString();
}
else if (!string.IsNullOrEmpty(text))
{
text = text.Remove(text.Length - 3);
: CharKey.ToString());
}
return string.Join(" + ", keys);
}
return text;
public bool Validate()
{
switch (CharKey)
{
case Key.LeftAlt:
case Key.RightAlt:
case Key.LeftCtrl:
case Key.RightCtrl:
case Key.LeftShift:
case Key.RightShift:
case Key.LWin:
case Key.RWin:
return false;
default:
if (ModifierKeys == ModifierKeys.None)
{
return !((CharKey >= Key.A && CharKey <= Key.Z) ||
(CharKey >= Key.D0 && CharKey <= Key.D9) ||
(CharKey >= Key.NumPad0 && CharKey <= Key.NumPad9));
}
else
{
return CharKey != Key.None;
}
}
}
public override bool Equals(object obj)
{
if (obj is HotkeyModel other)
{
return ModifierKeys == other.ModifierKeys && CharKey == other.CharKey;
}
else
{
return false;
}
}
public override int GetHashCode()
{
return HashCode.Combine(ModifierKeys, CharKey);
}
}
}

View file

@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Globalization;
using System.IO;
using System.Text.Json;
@ -11,62 +12,82 @@ namespace Flow.Launcher.Infrastructure.Storage
/// </summary>
public class JsonStorage<T> where T : new()
{
protected T _data;
protected T? Data;
// need a new directory name
public const string DirectoryName = "Settings";
public const string FileSuffix = ".json";
public string FilePath { get; set; }
public string DirectoryPath { get; set; }
protected string FilePath { get; init; } = null!;
private string TempFilePath => $"{FilePath}.tmp";
private string BackupFilePath => $"{FilePath}.bak";
protected string DirectoryPath { get; init; } = null!;
public T Load()
{
string? serialized = null;
if (File.Exists(FilePath))
{
var serialized = File.ReadAllText(FilePath);
if (!string.IsNullOrWhiteSpace(serialized))
serialized = File.ReadAllText(FilePath);
}
if (!string.IsNullOrEmpty(serialized))
{
try
{
Deserialize(serialized);
Data = JsonSerializer.Deserialize<T>(serialized) ?? TryLoadBackup() ?? LoadDefault();
}
else
catch (JsonException)
{
LoadDefault();
Data = TryLoadBackup() ?? LoadDefault();
}
}
else
{
LoadDefault();
Data = TryLoadBackup() ?? LoadDefault();
}
return _data.NonNull();
return Data.NonNull();
}
private void Deserialize(string serialized)
{
try
{
_data = JsonSerializer.Deserialize<T>(serialized);
}
catch (JsonException e)
{
LoadDefault();
Log.Exception($"|JsonStorage.Deserialize|Deserialize error for json <{FilePath}>", e);
}
if (_data == null)
{
LoadDefault();
}
}
private void LoadDefault()
private T LoadDefault()
{
if (File.Exists(FilePath))
{
BackupOriginFile();
}
_data = new T();
Save();
return new T();
}
private T? TryLoadBackup()
{
if (!File.Exists(BackupFilePath))
return default;
try
{
var data = JsonSerializer.Deserialize<T>(File.ReadAllText(BackupFilePath));
if (data != null)
{
Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully");
File.Replace(BackupFilePath, FilePath, null);
return data;
}
return default;
}
catch (JsonException)
{
return default;
}
}
private void BackupOriginFile()
@ -82,13 +103,22 @@ namespace Flow.Launcher.Infrastructure.Storage
public void Save()
{
string serialized = JsonSerializer.Serialize(_data, new JsonSerializerOptions() { WriteIndented = true });
string serialized = JsonSerializer.Serialize(Data,
new JsonSerializerOptions
{
WriteIndented = true
});
File.WriteAllText(FilePath, serialized);
File.WriteAllText(TempFilePath, serialized);
if (!File.Exists(FilePath))
{
File.Move(TempFilePath, FilePath);
}
else
{
File.Replace(TempFilePath, FilePath, BackupFilePath);
}
}
}
[Obsolete("Deprecated as of Flow Launcher v1.8.0, on 2021.06.21. " +
"This is used only for Everything plugin v1.4.9 or below backwards compatibility")]
public class JsonStrorage<T> : JsonStorage<T> where T : new() { }
}

View file

@ -18,7 +18,7 @@ namespace Flow.Launcher.Infrastructure.Storage
public PluginJsonStorage(T data) : this()
{
_data = data;
Data = data;
}
}
}

View file

@ -13,6 +13,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public class Settings : BaseModel
{
private string language = "en";
private string _theme = Constant.DefaultTheme;
public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}";
public string OpenResultModifiers { get; set; } = KeyConstant.Alt;
public string ColorScheme { get; set; } = "System";
@ -29,7 +30,18 @@ namespace Flow.Launcher.Infrastructure.UserSettings
OnPropertyChanged();
}
}
public string Theme { get; set; } = Constant.DefaultTheme;
public string Theme
{
get => _theme;
set
{
if (value == _theme)
return;
_theme = value;
OnPropertyChanged();
OnPropertyChanged(nameof(MaxResultsToShow));
}
}
public bool UseDropShadowEffect { get; set; } = false;
public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name;
public string QueryBoxFontStyle { get; set; }
@ -214,7 +226,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
}
}
public bool LeaveCmdOpen { get; set; }
public bool HideWhenDeactive { get; set; } = true;
public bool HideWhenDeactivated { get; set; } = true;
public SearchWindowPositions SearchWindowPosition { get; set; } = SearchWindowPositions.MouseScreenCenter;
public bool IgnoreHotkeysOnFullscreen { get; set; }

View file

@ -14,10 +14,10 @@
</PropertyGroup>
<PropertyGroup>
<Version>3.0.1</Version>
<PackageVersion>3.0.1</PackageVersion>
<AssemblyVersion>3.0.1</AssemblyVersion>
<FileVersion>3.0.1</FileVersion>
<Version>3.1.0</Version>
<PackageVersion>3.1.0</PackageVersion>
<AssemblyVersion>3.1.0</AssemblyVersion>
<FileVersion>3.1.0</FileVersion>
<PackageId>Flow.Launcher.Plugin</PackageId>
<Authors>Flow-Launcher</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
@ -65,7 +65,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
</ItemGroup>

View file

@ -1,7 +1,9 @@
using System;
using System.Diagnostics;
using System.IO;
#pragma warning disable IDE0005
using System.Windows;
#pragma warning restore IDE0005
namespace Flow.Launcher.Plugin.SharedCommands
{
@ -206,22 +208,16 @@ namespace Flow.Launcher.Plugin.SharedCommands
///</summary>
public static string GetPreviousExistingDirectory(Func<string, bool> locationExists, string path)
{
var previousDirectoryPath = "";
var index = path.LastIndexOf('\\');
if (index > 0 && index < (path.Length - 1))
{
previousDirectoryPath = path.Substring(0, index + 1);
if (!locationExists(previousDirectoryPath))
{
return "";
}
string previousDirectoryPath = path.Substring(0, index + 1);
return locationExists(previousDirectoryPath) ? previousDirectoryPath : "";
}
else
{
return "";
}
return previousDirectoryPath;
}
///<summary>
@ -241,5 +237,33 @@ namespace Flow.Launcher.Plugin.SharedCommands
return path;
}
/// <summary>
/// Returns if <paramref name="parentPath"/> contains <paramref name="subPath"/>.
/// From https://stackoverflow.com/a/66877016
/// </summary>
/// <param name="parentPath">Parent path</param>
/// <param name="subPath">Sub path</param>
/// <param name="allowEqual">If <see langword="true"/>, when <paramref name="parentPath"/> and <paramref name="subPath"/> are equal, returns <see langword="true"/></param>
/// <returns></returns>
public static bool PathContains(string parentPath, string subPath, bool allowEqual = false)
{
var rel = Path.GetRelativePath(parentPath.EnsureTrailingSlash(), subPath);
return (rel != "." || allowEqual)
&& rel != ".."
&& !rel.StartsWith("../")
&& !rel.StartsWith(@"..\")
&& !Path.IsPathRooted(rel);
}
/// <summary>
/// Returns path ended with "\"
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static string EnsureTrailingSlash(this string path)
{
return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
}
}
}

View file

@ -0,0 +1,53 @@
using Flow.Launcher.Plugin.SharedCommands;
using NUnit.Framework;
namespace Flow.Launcher.Test
{
[TestFixture]
public class FilesFoldersTest
{
// Testcases from https://stackoverflow.com/a/31941905/20703207
// Disk
[TestCase(@"c:", @"c:\foo", true)]
[TestCase(@"c:\", @"c:\foo", true)]
// Slash
[TestCase(@"c:\foo\bar\", @"c:\foo\", false)]
[TestCase(@"c:\foo\bar", @"c:\foo\", false)]
[TestCase(@"c:\foo", @"c:\foo\bar", true)]
[TestCase(@"c:\foo\", @"c:\foo\bar", true)]
// File
[TestCase(@"c:\foo", @"c:\foo\a.txt", true)]
[TestCase(@"c:\foo", @"c:/foo/a.txt", true)]
[TestCase(@"c:\FOO\a.txt", @"c:\foo", false)]
[TestCase(@"c:\foo\a.txt", @"c:\foo\", false)]
[TestCase(@"c:\foobar\a.txt", @"c:\foo", false)]
[TestCase(@"c:\foobar\a.txt", @"c:\foo\", false)]
[TestCase(@"c:\foo\", @"c:\foo.txt", false)]
// Prefix
[TestCase(@"c:\foo", @"c:\foobar", false)]
[TestCase(@"C:\Program", @"C:\Program Files\", false)]
[TestCase(@"c:\foobar", @"c:\foo\a.txt", false)]
[TestCase(@"c:\foobar\", @"c:\foo\a.txt", false)]
// Edge case
[TestCase(@"c:\foo", @"c:\foo\..\bar\baz", false)]
[TestCase(@"c:\bar", @"c:\foo\..\bar\baz", true)]
[TestCase(@"c:\barr", @"c:\foo\..\bar\baz", false)]
// Equality
[TestCase(@"c:\foo", @"c:\foo", false)]
[TestCase(@"c:\foo\", @"c:\foo", false)]
[TestCase(@"c:\foo", @"c:\foo\", false)]
public void GivenTwoPaths_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult)
{
Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path));
}
[TestCase(@"c:\foo", @"c:\foo", true)]
[TestCase(@"c:\foo\", @"c:\foo", true)]
[TestCase(@"c:\foo", @"c:\foo\", true)]
public void GivenTwoPathsAreTheSame_WhenCheckPathContains_ThenShouldBeTrue(string parentPath, string path, bool expectedResult)
{
Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path, true));
}
}
}

View file

@ -48,13 +48,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
</ItemGroup>
</Project>

View file

@ -7,9 +7,11 @@ using Flow.Launcher.Plugin.SharedCommands;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using static Flow.Launcher.Plugin.Explorer.Search.SearchManager;
namespace Flow.Launcher.Test.Plugins
{
@ -176,7 +178,7 @@ namespace Flow.Launcher.Test.Plugins
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
// When
var result = SearchManager.IsFileContentSearch(query.ActionKeyword);
var result = searchManager.IsFileContentSearch(query.ActionKeyword);
// Then
Assert.IsTrue(result,
@ -193,6 +195,7 @@ namespace Flow.Launcher.Test.Plugins
[TestCase(@"c:\>*", true)]
[TestCase(@"c:\>", true)]
[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)]
[TestCase(@"c:\SomeLocation\SomeOtherLocation", true)]
public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)
{
// When, Given
@ -393,5 +396,68 @@ namespace Flow.Launcher.Test.Plugins
// Then
Assert.AreEqual(result, expectedResult);
}
[TestCase(@"c:\foo", @"c:\foo", true)]
[TestCase(@"C:\Foo\", @"c:\foo\", true)]
[TestCase(@"c:\foo", @"c:\foo\", false)]
public void GivenTwoPaths_WhenCompared_ThenShouldBeExpectedSameOrDifferent(string path1, string path2, bool expectedResult)
{
// Given
var comparator = PathEqualityComparator.Instance;
var result1 = new Result
{
Title = Path.GetFileName(path1),
SubTitle = path1
};
var result2 = new Result
{
Title = Path.GetFileName(path2),
SubTitle = path2
};
// When, Then
Assert.AreEqual(expectedResult, comparator.Equals(result1, result2));
}
[TestCase(@"c:\foo\", @"c:\foo\")]
[TestCase(@"C:\Foo\", @"c:\foo\")]
public void GivenTwoPaths_WhenComparedHasCode_ThenShouldBeSame(string path1, string path2)
{
// Given
var comparator = PathEqualityComparator.Instance;
var result1 = new Result
{
Title = Path.GetFileName(path1),
SubTitle = path1
};
var result2 = new Result
{
Title = Path.GetFileName(path2),
SubTitle = path2
};
var hash1 = comparator.GetHashCode(result1);
var hash2 = comparator.GetHashCode(result2);
// When, Then
Assert.IsTrue(hash1 == hash2);
}
[TestCase(@"%appdata%", true)]
[TestCase(@"%appdata%\123", true)]
[TestCase(@"c:\foo %appdata%\", false)]
[TestCase(@"c:\users\%USERNAME%\downloads", true)]
[TestCase(@"c:\downloads", false)]
[TestCase(@"%", false)]
[TestCase(@"%%", false)]
[TestCase(@"%bla%blabla%", false)]
public void GivenPath_WhenHavingEnvironmentVariableOrNot_ThenShouldBeExpected(string path, bool expectedResult)
{
// When
var result = EnvironmentVariables.HasEnvironmentVar(path);
// Then
Assert.AreEqual(result, expectedResult);
}
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
@ -84,14 +85,14 @@ namespace Flow.Launcher
Current.MainWindow = window;
Current.MainWindow.Title = Constant.FlowLauncher;
HotKeyMapper.Initialize(_mainVM);
// happlebao todo temp fix for instance code logic
// todo temp fix for instance code logic
// load plugin before change language, because plugin language also needs be changed
InternationalizationManager.Instance.Settings = _settings;
InternationalizationManager.Instance.ChangeLanguage(_settings.Language);
// main windows needs initialized before theme change because of blur settigns
// main windows needs initialized before theme change because of blur settings
ThemeManager.Instance.Settings = _settings;
ThemeManager.Instance.ChangeTheme(_settings.Theme);
@ -130,20 +131,17 @@ namespace Flow.Launcher
//[Conditional("RELEASE")]
private void AutoUpdates()
{
Task.Run(async () =>
_ = Task.Run(async () =>
{
if (_settings.AutoUpdates)
{
// check udpate every 5 hours
var timer = new Timer(1000 * 60 * 60 * 5);
timer.Elapsed += async (s, e) =>
{
await _updater.UpdateAppAsync(API);
};
timer.Start();
// check updates on startup
// check update every 5 hours
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
await _updater.UpdateAppAsync(API);
while (await timer.WaitForNextTickAsync())
// check updates on startup
await _updater.UpdateAppAsync(API);
}
});
}

View file

@ -0,0 +1,25 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Flow.Launcher.Converters
{
public class DiameterToCenterPointConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double d)
{
return new Point(d / 2, d / 2);
}
return new Point(0, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View file

@ -0,0 +1,27 @@
using System;
using System.Globalization;
using System.Windows.Data;
using Windows.Devices.PointOfService;
namespace Flow.Launcher.Converters
{
public class IconRadiusConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length != 2)
throw new ArgumentException("IconRadiusConverter must have 2 parameters");
return values[1] switch
{
true => (double)values[0] / 2,
false => (double)values[0],
_ => throw new ArgumentException("The second argument should be boolean", nameof(values))
};
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View file

@ -90,7 +90,7 @@
</PackageReference>
<PackageReference Include="InputSimulator" Version="1.0.4" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageReference Include="ModernWpfUI" Version="0.9.4" />
<PackageReference Include="ModernWpfUI" Version="0.9.6" />
<PackageReference Include="NHotkey.Wpf" Version="2.1.0" />
<PackageReference Include="NuGet.CommandLine" Version="6.3.1">
<PrivateAssets>all</PrivateAssets>

View file

@ -48,6 +48,7 @@
Margin="0,0,18,0"
VerticalContentAlignment="Center"
input:InputMethod.IsInputMethodEnabled="False"
GotFocus="tbHotkey_GotFocus"
LostFocus="tbHotkey_LostFocus"
PreviewKeyDown="TbHotkey_OnPreviewKeyDown"
TabIndex="100" />

View file

@ -9,16 +9,11 @@ using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Plugin;
using System.Threading;
using System.Windows.Interop;
namespace Flow.Launcher
{
public partial class HotkeyControl : UserControl
{
private Brush tbMsgForegroundColorOriginal;
private string tbMsgTextOriginal;
public HotkeyModel CurrentHotkey { get; private set; }
public bool CurrentHotkeyAvailable { get; private set; }
@ -29,8 +24,6 @@ namespace Flow.Launcher
public HotkeyControl()
{
InitializeComponent();
tbMsgTextOriginal = tbMsg.Text;
tbMsgForegroundColorOriginal = tbMsg.Foreground;
}
private CancellationTokenSource hotkeyUpdateSource;
@ -55,9 +48,7 @@ namespace Flow.Launcher
specialKeyState.CtrlPressed,
key);
var hotkeyString = hotkeyModel.ToString();
if (hotkeyString == tbHotkey.Text)
if (hotkeyModel.Equals(CurrentHotkey))
{
return;
}
@ -72,33 +63,32 @@ namespace Flow.Launcher
public async Task SetHotkeyAsync(HotkeyModel keyModel, bool triggerValidate = true)
{
CurrentHotkey = keyModel;
tbHotkey.Text = CurrentHotkey.ToString();
tbHotkey.Text = keyModel.ToString();
tbHotkey.Select(tbHotkey.Text.Length, 0);
if (triggerValidate)
{
CurrentHotkeyAvailable = CheckHotkeyAvailability();
if (!CurrentHotkeyAvailable)
{
tbMsg.Foreground = new SolidColorBrush(Colors.Red);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
}
else
{
tbMsg.Foreground = new SolidColorBrush(Colors.Green);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success");
}
tbMsg.Visibility = Visibility.Visible;
bool hotkeyAvailable = CheckHotkeyAvailability(keyModel);
CurrentHotkeyAvailable = hotkeyAvailable;
SetMessage(hotkeyAvailable);
OnHotkeyChanged();
var token = hotkeyUpdateSource.Token;
await Task.Delay(500, token);
if (token.IsCancellationRequested)
return;
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null);
Keyboard.ClearFocus();
if (CurrentHotkeyAvailable)
{
CurrentHotkey = keyModel;
// To trigger LostFocus
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null);
Keyboard.ClearFocus();
}
}
else
{
CurrentHotkey = keyModel;
}
}
@ -107,14 +97,40 @@ namespace Flow.Launcher
return SetHotkeyAsync(new HotkeyModel(keyStr), triggerValidate);
}
private bool CheckHotkeyAvailability() => HotKeyMapper.CheckAvailability(CurrentHotkey);
private static bool CheckHotkeyAvailability(HotkeyModel hotkey) => hotkey.Validate() && HotKeyMapper.CheckAvailability(hotkey);
public new bool IsFocused => tbHotkey.IsFocused;
private void tbHotkey_LostFocus(object sender, RoutedEventArgs e)
{
tbMsg.Text = tbMsgTextOriginal;
tbHotkey.Text = CurrentHotkey?.ToString() ?? "";
tbHotkey.Select(tbHotkey.Text.Length, 0);
}
private void tbHotkey_GotFocus(object sender, RoutedEventArgs e)
{
ResetMessage();
}
private void ResetMessage()
{
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("flowlauncherPressHotkey");
tbMsg.SetResourceReference(TextBox.ForegroundProperty, "Color05B");
}
private void SetMessage(bool hotkeyAvailable)
{
if (!hotkeyAvailable)
{
tbMsg.Foreground = new SolidColorBrush(Colors.Red);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
}
else
{
tbMsg.Foreground = new SolidColorBrush(Colors.Green);
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success");
}
tbMsg.Visibility = Visibility.Visible;
}
}
}

View file

@ -9,11 +9,11 @@
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
Name="FlowMainWindow"
Title="Flow Launcher"
MinWidth="{Binding MainWindowWidth, Mode=OneWay}"
MaxWidth="{Binding MainWindowWidth, Mode=OneWay}"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
AllowDrop="True"
AllowsTransparency="True"
Background="Transparent"
@ -38,9 +38,9 @@
<converters:QuerySuggestionBoxConverter x:Key="QuerySuggestionBoxConverter" />
<converters:BorderClipConverter x:Key="BorderClipConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolToIMEConversionModeConverter x:Key="BoolToIMEConversionModeConverter"/>
<converters:BoolToIMEStateConverter x:Key="BoolToIMEStateConverter"/>
<converters:StringToKeyBindingConverter x:Key="StringToKeyBindingConverter"/>
<converters:BoolToIMEConversionModeConverter x:Key="BoolToIMEConversionModeConverter" />
<converters:BoolToIMEStateConverter x:Key="BoolToIMEStateConverter" />
<converters:StringToKeyBindingConverter x:Key="StringToKeyBindingConverter" />
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="Escape" Command="{Binding EscCommand}" />
@ -180,11 +180,11 @@
<KeyBinding
Key="F12"
Command="{Binding ToggleGameModeCommand}"
Modifiers="Ctrl"/>
Modifiers="Ctrl" />
<KeyBinding
Key="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding TogglePreviewCommand}"
Modifiers="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}"/>
Modifiers="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
</Window.InputBindings>
<Grid>
<Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}">
@ -207,12 +207,12 @@
<TextBox
x:Name="QueryTextBox"
AllowDrop="True"
InputMethod.PreferredImeConversionMode="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEConversionModeConverter}}"
InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}"
PreviewDragOver="OnPreviewDragOver"
PreviewKeyUp="QueryTextBox_KeyUp"
Style="{DynamicResource QueryBoxStyle}"
Text="{Binding QueryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
InputMethod.PreferredImeConversionMode="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEConversionModeConverter}}"
InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}"
Visibility="Visible">
<TextBox.CommandBindings>
<CommandBinding Command="ApplicationCommands.Copy" Executed="OnCopy" />
@ -273,9 +273,6 @@
<Grid>
<Image
x:Name="PluginActivationIcon"
Width="32"
Height="32"
Margin="0,0,18,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Panel.ZIndex="2"
@ -403,10 +400,10 @@
VerticalAlignment="Stretch"
Style="{DynamicResource PreviewArea}"
Visibility="{Binding PreviewVisible, Converter={StaticResource BoolToVisibilityConverter}}">
<Border
Style="{DynamicResource PreviewBorderStyle}"
<Border
d:DataContext="{d:DesignInstance vm:ResultViewModel}"
DataContext="{Binding SelectedItem, ElementName=ResultListBox}"
Style="{DynamicResource PreviewBorderStyle}"
Visibility="{Binding ShowDefaultPreview}">
<Grid
Margin="20,0,10,0"
@ -478,10 +475,10 @@
</StackPanel>
</Grid>
</Border>
<Border
<Border
d:DataContext="{d:DesignInstance vm:ResultViewModel}"
DataContext="{Binding SelectedItem, ElementName=ResultListBox}"
Style="{DynamicResource PreviewBorderStyle}"
Style="{DynamicResource PreviewBorderStyle}"
Visibility="{Binding ShowCustomizedPreview}">
<ContentControl Content="{Binding Result.PreviewPanel.Value}" />
</Border>

View file

@ -485,7 +485,7 @@ namespace Flow.Launcher
if (_settings.UseAnimation)
await Task.Delay(100);
if (_settings.HideWhenDeactive)
if (_settings.HideWhenDeactivated)
{
_viewModel.Hide();
}

View file

@ -85,6 +85,8 @@
<ui:NumberBox
x:Name="tbAction"
Width="200"
Maximum="100"
Minimum="-100"
Margin="10,0,15,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"

View file

@ -27,7 +27,7 @@ namespace Flow.Launcher.Resources.Pages
tbMsgTextOriginal = HotkeyControl.tbMsg.Text;
tbMsgForegroundColorOriginal = HotkeyControl.tbMsg.Foreground;
HotkeyControl.SetHotkeyAsync(new Infrastructure.Hotkey.HotkeyModel(Settings.Hotkey), false);
HotkeyControl.SetHotkeyAsync(Settings.Hotkey, false);
}
private void HotkeyControl_OnGotFocus(object sender, RoutedEventArgs args)
{
@ -49,4 +49,4 @@ namespace Flow.Launcher.Resources.Pages
HotkeyControl.tbMsg.Foreground = tbMsgForegroundColorOriginal;
}
}
}
}

View file

@ -27,10 +27,14 @@
Style="{DynamicResource BaseListboxStyle}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
Visibility="{Binding Visbility}"
Visibility="{Binding Visibility}"
mc:Ignorable="d">
<!-- IsSynchronizedWithCurrentItem: http://stackoverflow.com/a/7833798/2833083 -->
<ListBox.Resources>
<converter:IconRadiusConverter x:Key="IconRadiusConverter" />
<converter:DiameterToCenterPointConverter x:Key="DiameterToCenterPointConverter" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Button HorizontalAlignment="Stretch">
@ -52,7 +56,7 @@
<converter:OpenResultHotkeyVisibilityConverter x:Key="OpenResultHotkeyVisibilityConverter" />
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60" />
<ColumnDefinition Style="{DynamicResource ImageAreaWidth}" />
<ColumnDefinition Width="9*" />
<ColumnDefinition Width="Auto" MinWidth="8" />
</Grid.ColumnDefinitions>
@ -99,20 +103,29 @@
BorderThickness="1">
<Image
x:Name="ImageIcon"
Width="{Binding IconXY}"
Height="{Binding IconXY}"
Margin="0,0,0,0"
HorizontalAlignment="Center"
IsHitTestVisible="False"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding Image, TargetNullValue={x:Null}}"
Stretch="Uniform"
Style="{DynamicResource ImageIconStyle}"
Visibility="{Binding ShowIcon}">
<Image.Clip>
<EllipseGeometry
Center="16 16"
RadiusX="{Binding IconRadius}"
RadiusY="{Binding IconRadius}" />
<EllipseGeometry Center="{Binding ElementName=ImageIcon, Path=ActualWidth, Converter={StaticResource DiameterToCenterPointConverter}}">
<EllipseGeometry.RadiusX>
<MultiBinding Converter="{StaticResource IconRadiusConverter}">
<Binding ElementName="ImageIcon" Path="ActualWidth" />
<Binding Path="Result.RoundedIcon" />
</MultiBinding>
</EllipseGeometry.RadiusX>
<EllipseGeometry.RadiusY>
<MultiBinding Converter="{StaticResource IconRadiusConverter}">
<Binding ElementName="ImageIcon" Path="ActualWidth" />
<Binding Path="Result.RoundedIcon" />
</MultiBinding>
</EllipseGeometry.RadiusY>
</EllipseGeometry>
</Image.Clip>
</Image>
</Border>
@ -196,7 +209,6 @@
<Setter TargetName="Title" Property="Style" Value="{DynamicResource ItemTitleSelectedStyle}" />
<Setter TargetName="SubTitle" Property="Style" Value="{DynamicResource ItemSubTitleSelectedStyle}" />
<Setter TargetName="Hotkey" Property="Style" Value="{DynamicResource ItemHotkeySelectedStyle}" />
<Setter TargetName="ImageIcon" Property="Style" Value="{DynamicResource ItemImageSelectedStyle}" />
<Setter TargetName="GlyphIcon" Property="Style" Value="{DynamicResource ItemGlyphSelectedStyle}" />
</DataTrigger>
</DataTemplate.Triggers>
@ -207,7 +219,7 @@
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseEnter" Handler="OnMouseEnter" />
<EventSetter Event="MouseMove" Handler="OnMouseMove" />
<Setter Property="Height" Value="52" />
<Setter Property="Height" Value="{DynamicResource ResultItemHeight}" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="BorderThickness" Value="0" />
@ -240,4 +252,4 @@
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</ListBox>

View file

@ -673,7 +673,7 @@
<ui:ToggleSwitch
Grid.Column="2"
FocusVisualMargin="5"
IsOn="{Binding Settings.HideWhenDeactive}"
IsOn="{Binding Settings.HideWhenDeactivated}"
Style="{DynamicResource SideToggleSwitch}" />
</ItemsControl>
</Border>
@ -1821,7 +1821,7 @@
</Border.Clip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="54" />
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
@ -2438,7 +2438,7 @@
<Border Margin="0,8,0,0" Style="{DynamicResource SettingGroupBox}">
<ItemsControl Style="{StaticResource SettingGrid}">
<StackPanel Style="{StaticResource TextPanel}">
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource previewHotkey}"/>
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource previewHotkey}" />
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource previewHotkeyToolTip}" />
</StackPanel>
<flowlauncher:HotkeyControl

View file

@ -6,7 +6,9 @@
<CornerRadius x:Key="ItemRadius">0</CornerRadius>
<Thickness x:Key="ItemMargin">0</Thickness>
<Thickness x:Key="ResultMargin">0</Thickness>
<!-- Further font customisations are dynamically loaded in Theme.cs -->
<!-- Further font customizations are dynamically loaded in Theme.cs -->
<system:Double x:Key="ResultItemHeight">52</system:Double>
<Style x:Key="BaseBulletStyle" TargetType="{x:Type Border}" />
<Style
@ -17,6 +19,10 @@
x:Key="ItemBulletSelectedStyle"
BasedOn="{StaticResource BaseBulletStyle}"
TargetType="{x:Type Border}" />
<Style x:Key="ImageIconStyle" TargetType="{x:Type Image}">
<Setter Property="Height" Value="32" />
<Setter Property="Width" Value="32" />
</Style>
<Style x:Key="BaseQueryBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="BorderThickness" Value="0" />
@ -301,7 +307,7 @@
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<!-- Scrollbar in thr rigth of ScrollViewer -->
<!-- Scrollbar in the right of ScrollViewer -->
<ScrollBar
x:Name="PART_VerticalScrollBar"
Grid.Row="0"
@ -385,6 +391,12 @@
<Setter Property="FontSize" Value="15" />
<Setter Property="Foreground" Value="#8f8f8f" />
</Style>
<Style x:Key="BaseItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15" />
<Setter Property="Foreground" Value="#8f8f8f" />
</Style>
<!-- DO NOT USE THIS KEY. this key for themes with wrong typo. This key should be removed. Right key is BaseItemHotkeySelectedStyle. -->
<Style x:Key="BaseItemHotkeySelecetedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15" />
<Setter Property="Foreground" Value="#8f8f8f" />
@ -425,6 +437,9 @@
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ImageAreaWidth" TargetType="{x:Type ColumnDefinition}">
<Setter Property="Width" Value="60" />
</Style>
<Style
x:Key="PreviewGlyph"
BasedOn="{StaticResource BasePreviewGlyph}"
@ -464,6 +479,14 @@
</Style.Triggers>
</Style>
<!-- for classic themes -->
<Style x:Key="PluginActivationIcon" TargetType="{x:Type Image}">
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
<Setter Property="Margin" Value="0,0,18,0" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style x:Key="SearchIconStyle" TargetType="{x:Type Path}">
<Setter Property="Fill" Value="#555555" />
<Setter Property="Width" Value="32" />
@ -492,7 +515,7 @@
</Style>
<Style
x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelecetedStyle}"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15" />
<Setter Property="Foreground" Value="#8f8f8f" />

View file

@ -106,7 +106,7 @@
</Style>
<Style
x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelecetedStyle}"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="13" />
<Setter Property="Foreground" Value="#ff79c6" />

View file

@ -0,0 +1,217 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
</ResourceDictionary.MergedDictionaries>
<system:Double x:Key="ResultItemHeight">38</system:Double>
<Style x:Key="ImageAreaWidth" TargetType="{x:Type ColumnDefinition}">
<Setter Property="Width" Value="46" />
</Style>
<Style x:Key="ImageIconStyle" TargetType="{x:Type Image}">
<Setter Property="Height" Value="24" />
<Setter Property="Width" Value="24" />
</Style>
<Style
x:Key="ItemGlyph"
BasedOn="{StaticResource BaseGlyphStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="24" />
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
<Setter Property="Foreground" Value="#100f0f" />
</Style>
<Style
x:Key="QueryBoxStyle"
BasedOn="{StaticResource BaseQueryBoxStyle}"
TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0,2,42,0" />
<Setter Property="Foreground" Value="#282728" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Height" Value="24" />
</Style>
<Style
x:Key="QuerySuggestionBoxStyle"
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="0,2,42,0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="24" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Foreground" Value="#8f8c8f" />
</Style>
<Style
x:Key="WindowBorderStyle"
BasedOn="{StaticResource BaseWindowBorderStyle}"
TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="#bcbabd" />
<Setter Property="Background" Value="#edebee" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="UseLayoutRounding" Value="True" />
</Style>
<Style
x:Key="WindowStyle"
BasedOn="{StaticResource BaseWindowStyle}"
TargetType="{x:Type Window}" />
<Style
x:Key="PendingLineStyle"
BasedOn="{StaticResource BasePendingLineStyle}"
TargetType="{x:Type Line}" />
<!-- Item Style -->
<Style
x:Key="ItemTitleStyle"
BasedOn="{StaticResource BaseItemTitleStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="14" />
<Setter Property="Foreground" Value="#100f0f" />
</Style>
<Style
x:Key="ItemSubTitleStyle"
BasedOn="{StaticResource BaseItemSubTitleStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="10" />
<Setter Property="Foreground" Value="#8f8d90" />
</Style>
<Style
x:Key="SeparatorStyle"
BasedOn="{StaticResource BaseSeparatorStyle}"
TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="#dedcde" />
<Setter Property="Height" Value="1" />
<Setter Property="Margin" Value="0,0,0,4" />
</Style>
<Style x:Key="HighlightStyle" />
<Style
x:Key="ItemTitleSelectedStyle"
BasedOn="{StaticResource BaseItemTitleSelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="14" />
<Setter Property="Foreground" Value="#100f0f" />
</Style>
<Style
x:Key="ItemSubTitleSelectedStyle"
BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="10" />
<Setter Property="Foreground" Value="#8f8d90" />
</Style>
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#d6d4d7</SolidColorBrush>
<!-- button style in the middle of the scrollbar -->
<Style
x:Key="ThumbStyle"
BasedOn="{StaticResource BaseThumbStyle}"
TargetType="{x:Type Thumb}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border
Width="4"
Margin="0,0,2,0"
Background="#878687"
BorderBrush="Transparent"
BorderThickness="0"
CornerRadius="2"
DockPanel.Dock="Right" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style
x:Key="ScrollBarStyle"
BasedOn="{StaticResource BaseScrollBarStyle}"
TargetType="{x:Type ScrollBar}" />
<Style
x:Key="SearchIconStyle"
BasedOn="{StaticResource BaseSearchIconStyle}"
TargetType="{x:Type Path}">
<Setter Property="Fill" Value="#d5d3d6" />
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
</Style>
<Style x:Key="SearchIconPosition" TargetType="{x:Type Canvas}">
<Setter Property="Background" Value="#edebee" />
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
<Setter Property="Margin" Value="0,0,8,0" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style x:Key="PluginActivationIcon" TargetType="{x:Type Image}">
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
<Setter Property="Margin" Value="0,0,8,0" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="#8f8d90" />
</Style>
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="#8f8d90" />
</Style>
<Style x:Key="ItemGlyphSelectedStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#100f0f" />
<Setter Property="Width" Value="24" />
<Setter Property="Height" Value="24" />
<Setter Property="FontSize" Value="24" />
</Style>
<CornerRadius x:Key="ItemRadius">6</CornerRadius>
<Thickness x:Key="ItemMargin">4 0 4 0</Thickness>
<Thickness x:Key="ResultMargin">0 0 0 4</Thickness>
<Style
x:Key="ClockPanel"
BasedOn="{StaticResource ClockPanel}"
TargetType="{x:Type StackPanel}">
<Setter Property="Orientation" Value="Horizontal" />
<Setter Property="Margin" Value="0,0,42,0" />
</Style>
<Style
x:Key="ClockBox"
BasedOn="{StaticResource BaseClockBox}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="14" />
<Setter Property="Foreground" Value="#8f8d90" />
<Setter Property="Margin" Value="0,0,10,0" />
</Style>
<Style
x:Key="DateBox"
BasedOn="{StaticResource BaseDateBox}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="14" />
<Setter Property="Foreground" Value="#8f8d90" />
</Style>
<Style
x:Key="PreviewBorderStyle"
BasedOn="{StaticResource BasePreviewBorderStyle}"
TargetType="{x:Type Border}">
<Setter Property="Margin" Value="0,0,10,8" />
<Setter Property="BorderBrush" Value="#dedcde" />
</Style>
<Style
x:Key="PreviewItemTitleStyle"
BasedOn="{StaticResource BasePreviewItemTitleStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#100f0f" />
</Style>
<Style
x:Key="PreviewItemSubTitleStyle"
BasedOn="{StaticResource BasePreviewItemSubTitleStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#8f8d90" />
</Style>
<Style
x:Key="PreviewGlyph"
BasedOn="{StaticResource BasePreviewGlyph}"
TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="#100f0f" />
</Style>
</ResourceDictionary>

View file

@ -120,7 +120,7 @@
</Style>
<Style
x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelecetedStyle}"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="13" />
<Setter Property="Foreground" Value="#acacac" />

View file

@ -113,7 +113,7 @@
</Style>
<Style
x:Key="ItemHotkeySelectedStyle"
BasedOn="{StaticResource BaseItemHotkeySelecetedStyle}"
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="13" />
<Setter Property="Foreground" Value="{DynamicResource HotkeySelectedForeground}" />

View file

@ -535,13 +535,13 @@ namespace Flow.Launcher.ViewModel
_selectedResults = value;
if (SelectedIsFromQueryResults())
{
ContextMenu.Visbility = Visibility.Collapsed;
History.Visbility = Visibility.Collapsed;
ContextMenu.Visibility = Visibility.Collapsed;
History.Visibility = Visibility.Collapsed;
ChangeQueryText(_queryTextBeforeLeaveResults);
}
else
{
Results.Visbility = Visibility.Collapsed;
Results.Visibility = Visibility.Collapsed;
_queryTextBeforeLeaveResults = QueryText;
@ -559,7 +559,7 @@ namespace Flow.Launcher.ViewModel
}
}
_selectedResults.Visbility = Visibility.Visible;
_selectedResults.Visibility = Visibility.Visible;
}
}
@ -710,7 +710,7 @@ namespace Flow.Launcher.ViewModel
if (query == null) // shortcut expanded
{
Results.Clear();
Results.Visbility = Visibility.Collapsed;
Results.Visibility = Visibility.Collapsed;
PluginIconPath = null;
SearchIconVisibility = Visibility.Visible;
return;
@ -841,15 +841,20 @@ namespace Flow.Launcher.ViewModel
queryBuilder.Replace('@' + shortcut.Key, shortcut.Expand());
}
string customExpanded = queryBuilder.ToString();
Application.Current.Dispatcher.Invoke(() =>
{
foreach (var shortcut in builtInShortcuts)
{
try
{
var expansion = shortcut.Expand();
queryBuilder.Replace(shortcut.Key, expansion);
queryBuilderTmp.Replace(shortcut.Key, expansion);
if (customExpanded.Contains(shortcut.Key))
{
var expansion = shortcut.Expand();
queryBuilder.Replace(shortcut.Key, expansion);
queryBuilderTmp.Replace(shortcut.Key, expansion);
}
}
catch (Exception e)
{

View file

@ -43,13 +43,13 @@ namespace Flow.Launcher.ViewModel
#region Properties
public int MaxHeight => MaxResults * 52;
public double MaxHeight => MaxResults * (double)Application.Current.FindResource("ResultItemHeight")!;
public int SelectedIndex { get; set; }
public ResultViewModel SelectedItem { get; set; }
public Thickness Margin { get; set; }
public Visibility Visbility { get; set; } = Visibility.Collapsed;
public Visibility Visibility { get; set; } = Visibility.Collapsed;
public ICommand RightClickResultCommand { get; init; }
public ICommand LeftClickResultCommand { get; init; }
@ -167,14 +167,14 @@ namespace Flow.Launcher.ViewModel
SelectedItem = Results[0];
}
switch (Visbility)
switch (Visibility)
{
case Visibility.Collapsed when Results.Count > 0:
SelectedIndex = 0;
Visbility = Visibility.Visible;
Visibility = Visibility.Visible;
break;
case Visibility.Visible when Results.Count == 0:
Visbility = Visibility.Collapsed;
Visibility = Visibility.Collapsed;
break;
}
}
@ -259,7 +259,7 @@ namespace Flow.Launcher.ViewModel
return;
// manually update event
// wpf use directx / double buffered already, so just reset all won't cause ui flickering
// wpf use DirectX / double buffered already, so just reset all won't cause ui flickering
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private void AddAll(List<ResultViewModel> Items)

View file

@ -325,18 +325,14 @@ namespace Flow.Launcher.ViewModel
public IList<PluginViewModel> PluginViewModels
{
get
{
var metadatas = PluginManager.AllPlugins
.OrderBy(x => x.Metadata.Disabled)
.ThenBy(y => y.Metadata.Name)
.Select(p => new PluginViewModel
{
PluginPair = p
})
.ToList();
return metadatas;
}
get => PluginManager.AllPlugins
.OrderBy(x => x.Metadata.Disabled)
.ThenBy(y => y.Metadata.Name)
.Select(p => new PluginViewModel
{
PluginPair = p
})
.ToList();
}
public IList<PluginStoreItemViewModel> ExternalPlugins
@ -407,7 +403,6 @@ namespace Flow.Launcher.ViewModel
get { return Settings.Theme; }
set
{
Settings.Theme = value;
ThemeManager.Instance.ChangeTheme(value);
if (ThemeManager.Instance.BlurEnabled && Settings.UseDropShadowEffect)

View file

@ -3,8 +3,6 @@
using System;
using System.Threading.Tasks;
using System.Windows;
using Flow.Launcher.Plugin.Explorer.Search.IProvider;
using JetBrains.Annotations;
namespace Flow.Launcher.Plugin.Explorer.Exceptions;
@ -20,7 +18,7 @@ public class EngineNotAvailableException : Exception
string engineName,
string resolution,
string message,
Func<ActionContext, ValueTask<bool>> action = null) : base(message)
Func<ActionContext, ValueTask<bool>>? action = null) : base(message)
{
EngineName = engineName;
Resolution = resolution;
@ -40,6 +38,23 @@ public class EngineNotAvailableException : Exception
EngineName = engineName;
Resolution = resolution;
}
public EngineNotAvailableException(
string engineName,
string resolution,
string message,
string errorIconPath,
Func<ActionContext, ValueTask<bool>>? action = null) : base(message)
{
EngineName = engineName;
Resolution = resolution;
ErrorIcon = errorIconPath;
Action = action ?? (_ =>
{
Clipboard.SetDataObject(this.ToString());
return ValueTask.FromResult(true);
});
}
public override string ToString()
{

View file

@ -45,7 +45,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Data.OleDb" Version="5.0.0" />
<PackageReference Include="System.Data.OleDb" Version="7.0.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="tlbimp-Microsoft.Search.Interop" Version="1.0.0" />
</ItemGroup>

View file

@ -9,6 +9,7 @@
<system:String x:Key="plugin_explorer_delete_folder_link">Are you sure you want to delete {0}?</system:String>
<system:String x:Key="plugin_explorer_deletefolderconfirm">Are you sure you want to permanently delete this folder?</system:String>
<system:String x:Key="plugin_explorer_deletefileconfirm">Are you sure you want to permanently delete this file?</system:String>
<system:String x:Key="plugin_explorer_deletefilefolderconfirm">Are you sure you want to permanently delete this file/folder?</system:String>
<system:String x:Key="plugin_explorer_deletefilefoldersuccess">Deletion successful</system:String>
<system:String x:Key="plugin_explorer_deletefilefoldersuccess_detail">Successfully deleted {0}</system:String>
<system:String x:Key="plugin_explorer_globalActionKeywordInvalid">Assigning the global action keyword could bring up too many results during search. Please choose a specific action keyword</system:String>

View file

@ -32,7 +32,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal const string DefaultContentSearchActionKeyword = "doc:";
internal const char DirectorySeperator = '\\';
internal const char DirectorySeparator = '\\';
internal const string WindowsIndexingOptions = "srchadmin.dll";

View file

@ -15,7 +15,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
var criteria = ConstructSearchCriteria(search);
if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) >
search.LastIndexOf(Constants.DirectorySeperator))
search.LastIndexOf(Constants.DirectorySeparator))
return DirectorySearch(new EnumerationOptions
{
RecurseSubdirectories = true
@ -29,9 +29,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
{
string incompleteName = "";
if (!search.EndsWith(Constants.DirectorySeperator))
if (!search.EndsWith(Constants.DirectorySeparator))
{
var indexOfSeparator = search.LastIndexOf(Constants.DirectorySeperator);
var indexOfSeparator = search.LastIndexOf(Constants.DirectorySeparator);
incompleteName = search[(indexOfSeparator + 1)..].ToLower();

View file

@ -1,70 +1,75 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Plugin.Explorer.Search
{
public static class EnvironmentVariables
{
internal static bool IsEnvironmentVariableSearch(string search)
private static Dictionary<string, string> _envStringPaths = null;
private static Dictionary<string, string> EnvStringPaths
{
return search.StartsWith("%")
&& search != "%%"
&& !search.Contains("\\") &&
LoadEnvironmentStringPaths().Count > 0;
get
{
if (_envStringPaths == null)
{
LoadEnvironmentStringPaths();
}
return _envStringPaths;
}
}
internal static Dictionary<string, string> LoadEnvironmentStringPaths()
internal static bool IsEnvironmentVariableSearch(string search)
{
var envStringPaths = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
return search.StartsWith("%")
&& search != "%%"
&& !search.Contains('\\')
&& EnvStringPaths.Count > 0;
}
public static bool HasEnvironmentVar(string search)
{
// "c:\foo %appdata%\" returns false
var splited = search.Split(Path.DirectorySeparatorChar);
return splited.Any(dir => dir.StartsWith('%') &&
dir.EndsWith('%') &&
dir.Length > 2 &&
dir.Split('%').Length == 3);
}
private static void LoadEnvironmentStringPaths()
{
_envStringPaths = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
var homedrive = Environment.GetEnvironmentVariable("HOMEDRIVE")?.EnsureTrailingSlash() ?? "C:\\";
foreach (DictionaryEntry special in Environment.GetEnvironmentVariables())
{
var path = special.Value.ToString();
// we add a trailing slash to the path to make sure drive paths become valid absolute paths.
// for example, if %systemdrive% is C: we turn it to C:\
path = path.EnsureTrailingSlash();
// if we don't have an absolute path, we use Path.GetFullPath to get one.
// for example, if %homepath% is \Users\John we turn it to C:\Users\John
// Add basepath for GetFullPath() to parse %HOMEPATH% correctly
path = Path.IsPathFullyQualified(path) ? path : Path.GetFullPath(path, homedrive);
if (Directory.Exists(path))
{
// we add a trailing slash to the path to make sure drive paths become valid absolute paths.
// for example, if %systemdrive% is C: we turn it to C:\
path = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
// if we don't have an absolute path, we use Path.GetFullPath to get one.
// for example, if %homepath% is \Users\John we turn it to C:\Users\John
path = Path.IsPathFullyQualified(path) ? path : Path.GetFullPath(path);
// Variables are returned with a mixture of all upper/lower case.
// Call ToLower() to make the results look consistent
envStringPaths.Add(special.Key.ToString().ToLower(), path);
// Call ToUpper() to make the results look consistent
_envStringPaths.Add(special.Key.ToString().ToUpper(), path);
}
}
return envStringPaths;
}
internal static string TranslateEnvironmentVariablePath(string environmentVariablePath)
{
var envStringPaths = LoadEnvironmentStringPaths();
var splitSearch = environmentVariablePath.Substring(1).Split("%");
var exactEnvStringPath = splitSearch[0];
// if there are more than 2 % characters in the query, don't bother
if (splitSearch.Length == 2 && envStringPaths.ContainsKey(exactEnvStringPath))
{
var queryPartToReplace = $"%{exactEnvStringPath}%";
var expandedPath = envStringPaths[exactEnvStringPath];
// replace the %envstring% part of the query with its expanded equivalent
return environmentVariablePath.Replace(queryPartToReplace, expandedPath);
}
return environmentVariablePath;
}
internal static List<Result> GetEnvironmentStringPathSuggestions(string querySearch, Query query, PluginInitContext context)
{
var results = new List<Result>();
var environmentVariables = LoadEnvironmentStringPaths();
var search = querySearch;
if (querySearch.EndsWith("%") && search.Length > 1)
@ -72,12 +77,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
// query starts and ends with a %, find an exact match from env-string paths
search = querySearch.Substring(1, search.Length - 2);
if (environmentVariables.ContainsKey(search))
if (EnvStringPaths.ContainsKey(search))
{
var expandedPath = environmentVariables[search];
var expandedPath = EnvStringPaths[search];
results.Add(ResultManager.CreateFolderResult($"%{search}%", expandedPath, expandedPath, query));
return results;
}
}
@ -90,8 +95,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
search = search.Substring(1);
}
foreach (var p in environmentVariables)
foreach (var p in EnvStringPaths)
{
if (p.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase))
{

View file

@ -13,13 +13,12 @@ using Flow.Launcher.Plugin.Explorer.Exceptions;
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
{
public static class EverythingApi
{
private const int BufferSize = 4096;
private static SemaphoreSlim _semaphore = new(1, 1);
// cached buffer to remove redundant allocations.
private static readonly StringBuilder buffer = new(BufferSize);
@ -35,46 +34,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
InvalidCallError
}
/// <summary>
/// Gets or sets a value indicating whether [match path].
/// </summary>
/// <value><c>true</c> if [match path]; otherwise, <c>false</c>.</value>
public static bool MatchPath
{
get => EverythingApiDllImport.Everything_GetMatchPath();
set => EverythingApiDllImport.Everything_SetMatchPath(value);
}
/// <summary>
/// Gets or sets a value indicating whether [match case].
/// </summary>
/// <value><c>true</c> if [match case]; otherwise, <c>false</c>.</value>
public static bool MatchCase
{
get => EverythingApiDllImport.Everything_GetMatchCase();
set => EverythingApiDllImport.Everything_SetMatchCase(value);
}
/// <summary>
/// Gets or sets a value indicating whether [match whole word].
/// </summary>
/// <value><c>true</c> if [match whole word]; otherwise, <c>false</c>.</value>
public static bool MatchWholeWord
{
get => EverythingApiDllImport.Everything_GetMatchWholeWord();
set => EverythingApiDllImport.Everything_SetMatchWholeWord(value);
}
/// <summary>
/// Gets or sets a value indicating whether [enable regex].
/// </summary>
/// <value><c>true</c> if [enable regex]; otherwise, <c>false</c>.</value>
public static bool EnableRegex
{
get => EverythingApiDllImport.Everything_GetRegex();
set => EverythingApiDllImport.Everything_SetRegex(value);
}
/// <summary>
/// Checks whether the sort option is Fast Sort.
/// </summary>
@ -95,7 +54,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
try
{
EverythingApiDllImport.Everything_GetMajorVersion();
EverythingApiDllImport.Everything_GetMajorVersion();
var result = EverythingApiDllImport.Everything_GetLastError() != StateCode.IPCError;
return result;
}
@ -122,7 +81,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
await _semaphore.WaitAsync(token);
try
{
if (token.IsCancellationRequested)
@ -152,6 +111,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
EverythingApiDllImport.Everything_SetMax(option.MaxCount);
EverythingApiDllImport.Everything_SetSort(option.SortOption);
EverythingApiDllImport.Everything_SetMatchPath(option.IsFullPathSearch);
if (token.IsCancellationRequested) yield break;

View file

@ -27,20 +27,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_click_to_launch_or_install"),
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"),
ClickToInstallEverythingAsync)
{
ErrorIcon = Constants.EverythingErrorImagePath
};
Constants.EverythingErrorImagePath,
ClickToInstallEverythingAsync);
}
catch (DllNotFoundException)
{
throw new EngineNotAvailableException(
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
"Please check whether your system is x86 or x64",
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"))
{
ErrorIcon = Constants.GeneralSearchErrorImagePath
};
Constants.GeneralSearchErrorImagePath,
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"));
}
}
private async ValueTask<bool> ClickToInstallEverythingAsync(ActionContext _)
@ -72,16 +68,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
if (!Settings.EnableEverythingContentSearch)
{
throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
"Click to Enable Everything Content Search (only applicable to Everything 1.5+ with indexed content)",
"Everything Content Search is not enabled.",
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
Constants.EverythingErrorImagePath,
_ =>
{
Settings.EnableEverythingContentSearch = true;
return ValueTask.FromResult(true);
})
{
ErrorIcon = Constants.EverythingErrorImagePath
};
});
}
if (token.IsCancellationRequested)
yield break;

View file

@ -3,12 +3,15 @@ using Flow.Launcher.Plugin.Everything.Everything;
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
{
public record struct EverythingSearchOption(string Keyword,
public record struct EverythingSearchOption(
string Keyword,
SortOption SortOption,
bool IsContentSearch = false,
bool IsContentSearch = false,
string ContentSearchKeyword = default,
string ParentPath = default,
bool IsRecursive = true,
int Offset = 0,
int MaxCount = 100);
int Offset = 0,
int MaxCount = 100,
bool IsFullPathSearch = true
);
}

View file

@ -15,7 +15,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
{
get
{
var path = Path.EndsWith(Constants.DirectorySeperator) ? Path[0..^1] : Path;
var path = Path.EndsWith(Constants.DirectorySeparator) ? Path[0..^1] : Path;
if (path.EndsWith(':'))
return path[0..^1] + " Drive";

View file

@ -27,8 +27,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var usePathSearchActionKeyword = Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled;
var pathSearchActionKeyword = Settings.PathSearchActionKeyword == Query.GlobalPluginWildcardSign
? string.Empty
var pathSearchActionKeyword = Settings.PathSearchActionKeyword == Query.GlobalPluginWildcardSign
? string.Empty
: $"{Settings.PathSearchActionKeyword} ";
var searchActionKeyword = Settings.SearchActionKeyword == Query.GlobalPluginWildcardSign
@ -36,12 +36,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
: $"{Settings.SearchActionKeyword} ";
var keyword = usePathSearchActionKeyword ? pathSearchActionKeyword : searchActionKeyword;
var formatted_path = path;
if (type == ResultType.Folder)
// the seperator is needed so when navigating the folder structure contents of the folder are listed
formatted_path = path.EndsWith(Constants.DirectorySeperator) ? path : path + Constants.DirectorySeperator;
// the separator is needed so when navigating the folder structure contents of the folder are listed
formatted_path = path.EndsWith(Constants.DirectorySeparator) ? path : path + Constants.DirectorySeparator;
return $"{keyword}{formatted_path}";
}
@ -49,8 +49,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
public static string GetAutoCompleteText(string title, Query query, string path, ResultType resultType)
{
return !Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled
? $"{query.ActionKeyword} {title}" // Only Quick Access action keyword is used in this scenario
: GetPathWithActionKeyword(path, resultType, query.ActionKeyword);
? $"{query.ActionKeyword} {title}" // Only Quick Access action keyword is used in this scenario
: GetPathWithActionKeyword(path, resultType, query.ActionKeyword);
}
public static Result CreateResult(Query query, SearchResult result)
@ -71,7 +71,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
Title = title,
IcoPath = path,
SubTitle = Path.GetDirectoryName(path),
SubTitle = subtitle,
AutoCompleteText = GetAutoCompleteText(title, query, path, ResultType.Folder),
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
CopyText = path,
@ -187,9 +187,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal static Result CreateOpenCurrentFolderResult(string path, string actionKeyword, bool windowsIndexed = false)
{
// Path passed from PathSearchAsync ends with Constants.DirectorySeperator ('\'), need to remove the seperator
// Path passed from PathSearchAsync ends with Constants.DirectorySeparator ('\'), need to remove the separator
// so it's consistent with folder results returned by index search which does not end with one
var folderPath = path.TrimEnd(Constants.DirectorySeperator);
var folderPath = path.TrimEnd(Constants.DirectorySeparator);
return new Result
{
@ -215,9 +215,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal static Result CreateFileResult(string filePath, Query query, int score = 0, bool windowsIndexed = false)
{
Result.PreviewInfo preview = IsMedia(Path.GetExtension(filePath)) ? new Result.PreviewInfo {
IsMedia = true,
PreviewImagePath = filePath,
Result.PreviewInfo preview = IsMedia(Path.GetExtension(filePath)) ? new Result.PreviewInfo
{
IsMedia = true, PreviewImagePath = filePath,
} : Result.PreviewInfo.Default;
var title = Path.GetFileName(filePath);
@ -246,6 +246,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
FileName = filePath,
UseShellExecute = true,
WorkingDirectory = Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty,
Verb = "runas",
});
}
@ -286,8 +287,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
public static bool IsMedia(string extension)
{
if (string.IsNullOrEmpty(extension))
{
return false;
{
return false;
}
else
{
@ -295,7 +296,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search
}
}
public static readonly string[] MediaExtensions = { ".jpg", ".png", ".avi", ".mkv", ".bmp", ".gif", ".wmv", ".mp3", ".flac", ".mp4" };
public static readonly string[] MediaExtensions =
{
".jpg", ".png", ".avi", ".mkv", ".bmp", ".gif", ".wmv", ".mp3", ".flac", ".mp4"
};
}
public enum ResultType

View file

@ -13,9 +13,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
public class SearchManager
{
internal static PluginInitContext Context;
internal PluginInitContext Context;
internal static Settings Settings;
internal Settings Settings;
public SearchManager(Settings settings, PluginInitContext context)
{
@ -23,19 +23,23 @@ namespace Flow.Launcher.Plugin.Explorer.Search
Settings = settings;
}
private class PathEqualityComparator : IEqualityComparer<Result>
/// <summary>
/// Note: A path that ends with "\" and one that doesn't will not be regarded as equal.
/// </summary>
public class PathEqualityComparator : IEqualityComparer<Result>
{
private static PathEqualityComparator instance;
public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator();
public bool Equals(Result x, Result y)
{
return x.Title == y.Title && x.SubTitle == y.SubTitle;
return x.Title.Equals(y.Title, StringComparison.OrdinalIgnoreCase)
&& string.Equals(x.SubTitle, y.SubTitle, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(Result obj)
{
return HashCode.Combine(obj.Title.GetHashCode(), obj.SubTitle?.GetHashCode() ?? 0);
return HashCode.Combine(obj.Title.ToLowerInvariant(), obj.SubTitle?.ToLowerInvariant() ?? "");
}
}
@ -105,19 +109,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
results.Add(ResultManager.CreateResult(query, search));
}
catch (OperationCanceledException)
{
return new List<Result>();
}
catch (EngineNotAvailableException)
{
throw;
}
catch (Exception e)
{
if (e is OperationCanceledException)
return results.ToList();
if (e is EngineNotAvailableException)
throw;
throw new SearchException(engineName, e.Message, e);
}
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
excludedPath => r.SubTitle.StartsWith(excludedPath.Path, StringComparison.OrdinalIgnoreCase)));
excludedPath => FilesFolders.PathContains(excludedPath.Path, r.SubTitle)));
return results.ToList();
}
@ -142,7 +148,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
};
}
private static List<Result> EverythingContentSearchResult(Query query)
private List<Result> EverythingContentSearchResult(Query query)
{
return new List<Result>()
{
@ -167,18 +173,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var results = new HashSet<Result>(PathEqualityComparator.Instance);
var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch);
if (isEnvironmentVariable)
if (EnvironmentVariables.IsEnvironmentVariableSearch(querySearch))
return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, Context);
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\
var isEnvironmentVariablePath = querySearch[1..].Contains("%\\");
var locationPath = querySearch;
if (isEnvironmentVariablePath)
locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath);
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\, c:\users\%USERNAME%\downloads
var needToExpand = EnvironmentVariables.HasEnvironmentVar(querySearch);
var locationPath = needToExpand ? Environment.ExpandEnvironmentVariables(querySearch) : querySearch;
// Check that actual location exists, otherwise directory search will throw directory not found exception
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists())
@ -234,7 +234,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return results.ToList();
}
public static bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword;
public bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword;
private bool UseWindowsIndexForDirectorySearch(string locationPath)
@ -245,10 +245,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
&& WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory);
}
internal static bool IsEnvironmentVariableSearch(string search)
{
return search.StartsWith("%")
return search.StartsWith("%")
&& search != "%%"
&& !search.Contains('\\');
}

View file

@ -97,27 +97,25 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
private IAsyncEnumerable<SearchResult> HandledEngineNotAvailableExceptionAsync()
{
if (!SearchManager.Settings.WarnWindowsSearchServiceOff)
if (!Settings.WarnWindowsSearchServiceOff)
return AsyncEnumerable.Empty<SearchResult>();
var api = SearchManager.Context.API;
var api = Main.Context.API;
throw new EngineNotAvailableException(
"Windows Index",
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
Constants.WindowsIndexErrorImagePath,
c =>
{
SearchManager.Settings.WarnWindowsSearchServiceOff = false;
Settings.WarnWindowsSearchServiceOff = false;
// Clears the warning message so user is not mistaken that it has not worked
api.ChangeQuery(string.Empty);
return ValueTask.FromResult(false);
})
{
ErrorIcon = Constants.WindowsIndexErrorImagePath
};
});
}
}
}

View file

@ -10,7 +10,7 @@
"Name": "Explorer",
"Description": "Find and manage files and folders via Windows Search or Everything",
"Author": "Jeremy Wu",
"Version": "2.1.0",
"Version": "2.2.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",

View file

@ -1,10 +1,10 @@
{
"ID": "6A122269676E40EB86EB543B945932B9",
"ActionKeyword": "*",
"ActionKeyword": "?",
"Name": "Plugin Indicator",
"Description": "Provides plugin action keyword suggestions",
"Author": "qianlifeng",
"Version": "2.0.1",
"Version": "2.0.2",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.PluginIndicator.dll",

View file

@ -38,6 +38,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="SharpZipLib" Version="1.4.1" />
</ItemGroup>
</Project>

View file

@ -6,7 +6,7 @@
"Name": "Plugins Manager",
"Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
"Author": "Jeremy Wu",
"Version": "2.0.0",
"Version": "2.0.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",

View file

@ -378,15 +378,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
MatchResult matchResult;
// We suppose Name won't be null
if (!Main._settings.EnableDescription || Description == null || Name.StartsWith(Description))
if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || Name.Equals(Description))
{
title = Name;
matchResult = StringMatcher.FuzzySearch(query, title);
}
else if (Description.StartsWith(Name))
{
title = Description;
matchResult = StringMatcher.FuzzySearch(query, Description);
matchResult = StringMatcher.FuzzySearch(query, Name);
}
else
{
@ -401,15 +396,19 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
matchResult = descriptionMatch;
}
else matchResult = nameMatch;
else
{
matchResult = nameMatch;
}
}
if (!matchResult.Success)
if (!matchResult.IsSearchPrecisionScoreMet())
return null;
var result = new Result
{
Title = title,
AutoCompleteText = Name,
SubTitle = Main._settings.HideAppsPath ? string.Empty : Location,
IcoPath = LogoPath,
Preview = new Result.PreviewInfo

View file

@ -90,44 +90,28 @@ namespace Flow.Launcher.Plugin.Program.Programs
bool useLocalizedName = !string.IsNullOrEmpty(LocalizedName) && !Name.Equals(LocalizedName);
string resultName = useLocalizedName ? LocalizedName : Name;
if (!Main._settings.EnableDescription)
if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || resultName.Equals(Description))
{
title = resultName;
matchResult = StringMatcher.FuzzySearch(query, resultName);
}
else
{
if (string.IsNullOrEmpty(Description) || resultName.StartsWith(Description))
// Search in both
title = $"{resultName}: {Description}";
var nameMatch = StringMatcher.FuzzySearch(query, resultName);
var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
if (descriptionMatch.Score > nameMatch.Score)
{
// Description is invalid or included in resultName
// Description is always localized, so Name.StartsWith(Description) is generally useless
title = resultName;
matchResult = StringMatcher.FuzzySearch(query, resultName);
}
else if (Description.StartsWith(resultName))
{
// resultName included in Description
title = Description;
matchResult = StringMatcher.FuzzySearch(query, Description);
for (int i = 0; i < descriptionMatch.MatchData.Count; i++)
{
descriptionMatch.MatchData[i] += resultName.Length + 2; // 2 is ": "
}
matchResult = descriptionMatch;
}
else
{
// Search in both
title = $"{resultName}: {Description}";
var nameMatch = StringMatcher.FuzzySearch(query, resultName);
var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
if (descriptionMatch.Score > nameMatch.Score)
{
for (int i = 0; i < descriptionMatch.MatchData.Count; i++)
{
descriptionMatch.MatchData[i] += resultName.Length + 2; // 2 is ": "
}
matchResult = descriptionMatch;
}
else
{
matchResult = nameMatch;
}
matchResult = nameMatch;
}
}
@ -171,6 +155,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
var result = new Result
{
Title = title,
AutoCompleteText = resultName,
SubTitle = subtitle,
IcoPath = IcoPath,
Score = matchResult.Score,
@ -486,8 +471,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
var paths = pathEnv.Split(";", StringSplitOptions.RemoveEmptyEntries).DistinctBy(p => p.ToLowerInvariant());
var toFilter = paths.Where(x => commonParents.All(parent => !IsSubPathOf(x, parent)))
var toFilter = paths.Where(x => commonParents.All(parent => !FilesFolders.PathContains(parent, x)))
.AsParallel()
.SelectMany(p => EnumerateProgramsInDir(p, suffixes, recursive: false));
@ -779,17 +764,6 @@ namespace Flow.Launcher.Plugin.Program.Programs
}
}
// https://stackoverflow.com/a/66877016
private static bool IsSubPathOf(string subPath, string basePath)
{
var rel = Path.GetRelativePath(basePath, subPath);
return rel != "."
&& rel != ".."
&& !rel.StartsWith("../")
&& !rel.StartsWith(@"..\")
&& !Path.IsPathRooted(rel);
}
private static List<string> GetCommonParents(IEnumerable<ProgramSource> programSources)
{
// To avoid unnecessary io
@ -801,8 +775,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
HashSet<ProgramSource> parents = group.ToHashSet();
foreach (var source in group)
{
if (parents.Any(p => IsSubPathOf(source.Location, p.Location) &&
source != p))
if (parents.Any(p => FilesFolders.PathContains(p.Location, source.Location)))
{
parents.Remove(source);
}

View file

@ -4,7 +4,7 @@
"Name": "Program",
"Description": "Search programs in Flow.Launcher",
"Author": "qianlifeng",
"Version": "2.1.0",
"Version": "2.2.0",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Program.dll",

View file

@ -4,7 +4,7 @@
"Name": "Shell",
"Description": "Provide executing commands from Flow Launcher",
"Author": "qianlifeng",
"Version": "2.0.0",
"Version": "2.0.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Shell.dll",

View file

@ -4,7 +4,7 @@
"Name": "System Commands",
"Description": "Provide System related commands. e.g. shutdown,lock, setting etc.",
"Author": "qianlifeng",
"Version": "2.0.0",
"Version": "2.0.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll",

View file

@ -4,7 +4,7 @@
"Name": "URL",
"Description": "Open the typed URL from Flow Launcher",
"Author": "qianlifeng",
"Version": "2.0.0",
"Version": "2.0.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Url.dll",

View file

@ -26,7 +26,7 @@
"Name": "Web Searches",
"Description": "Provide the web search ability",
"Author": "qianlifeng",
"Version": "2.0.1",
"Version": "2.0.2",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.WebSearch.dll",

View file

@ -4,7 +4,7 @@
"Description": "Search settings inside Control Panel and Settings App",
"Name": "Windows Settings",
"Author": "TobiasSekan",
"Version": "3.0.1",
"Version": "3.0.2",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.WindowsSettings.dll",

View file

@ -330,6 +330,8 @@ And you can download <a href="https://github.com/Flow-Launcher/Flow.Launcher/dis
<a href="https://github.com/itsonlyfrans"><img src="https://avatars.githubusercontent.com/u/46535667?v=4" width="10%" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://github.com/andreqramos"><img src="https://avatars.githubusercontent.com/u/49326063?v=4" width="10%" /></a>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://github.com/patrickdobler"><img src="https://avatars.githubusercontent.com/u/16536946?v=4" width="10%" /></a>
</p>
### Mentions

View file

@ -1,4 +1,4 @@
version: '1.11.0.{build}'
version: '1.12.1.{build}'
init:
- ps: |