Merge branch 'dev' into address_high_memory_issue

This commit is contained in:
Jeremy Wu 2020-11-11 08:29:53 +11:00
commit d0cb8021c6
94 changed files with 1238 additions and 623 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 58 KiB

BIN
Doc/app_missing_img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -95,7 +95,7 @@ namespace Flow.Launcher.Core.Configuration
public void MoveUserDataFolder(string fromLocation, string toLocation)
{
FilesFolders.Copy(fromLocation, toLocation);
FilesFolders.CopyAll(fromLocation, toLocation);
VerifyUserDataAfterMove(fromLocation, toLocation);
}

View file

@ -29,7 +29,6 @@ namespace Flow.Launcher.Core.Plugin
else
{ // non action keyword
actionKeyword = string.Empty;
actionParameters = terms.ToList();
search = rawQuery;
}
@ -38,10 +37,7 @@ namespace Flow.Launcher.Core.Plugin
Terms = terms,
RawQuery = rawQuery,
ActionKeyword = actionKeyword,
Search = search,
// Obsolete value initialisation
ActionName = actionKeyword,
ActionParameters = actionParameters
Search = search
};
return query;

View file

@ -91,7 +91,7 @@ namespace Flow.Launcher.Core
if (DataLocation.PortableDataLocationInUse())
{
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
FilesFolders.Copy(DataLocation.PortableDataPath, targetDestination);
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination);
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination))
MessageBox.Show("Flow Launcher was not able to move your user profile data to the new update version. Please manually " +
$"move your profile data folder from {DataLocation.PortableDataPath} to {targetDestination}");

View file

@ -1,199 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using hyjiacan.util.p4n;
using hyjiacan.util.p4n.format;
using JetBrains.Annotations;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
namespace Flow.Launcher.Infrastructure
{
public interface IAlphabet
{
string Translate(string stringToTranslate);
}
public class Alphabet : IAlphabet
{
private readonly HanyuPinyinOutputFormat Format = new HanyuPinyinOutputFormat();
private ConcurrentDictionary<string, string[][]> PinyinCache;
private BinaryStorage<Dictionary<string, string[][]>> _pinyinStorage;
private Settings _settings;
public void Initialize([NotNull] Settings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
InitializePinyinHelpers();
}
private void InitializePinyinHelpers()
{
Format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
Stopwatch.Normal("|Flow Launcher.Infrastructure.Alphabet.Initialize|Preload pinyin cache", () =>
{
_pinyinStorage = new BinaryStorage<Dictionary<string, string[][]>>("Pinyin");
lock(_pinyinStorage)
{
var loaded = _pinyinStorage.TryLoad(new Dictionary<string, string[][]>());
PinyinCache = new ConcurrentDictionary<string, string[][]>(loaded);
}
// force pinyin library static constructor initialize
PinyinHelper.toHanyuPinyinStringArray('T', Format);
});
Log.Info($"|Flow Launcher.Infrastructure.Alphabet.Initialize|Number of preload pinyin combination<{PinyinCache.Count}>");
}
public string Translate(string str)
{
return ConvertChineseCharactersToPinyin(str);
}
public string ConvertChineseCharactersToPinyin(string source)
{
if (!_settings.ShouldUsePinyin)
return source;
if (string.IsNullOrEmpty(source))
return source;
if (!ContainsChinese(source))
return source;
var combination = PinyinCombination(source);
var pinyinArray=combination.Select(x => string.Join("", x));
var acronymArray = combination.Select(Acronym).Distinct();
var joinedSingleStringCombination = new StringBuilder();
var all = acronymArray.Concat(pinyinArray);
all.ToList().ForEach(x => joinedSingleStringCombination.Append(x));
return joinedSingleStringCombination.ToString();
}
public void Save()
{
if (!_settings.ShouldUsePinyin)
{
return;
}
lock(_pinyinStorage)
{
_pinyinStorage.Save(PinyinCache.ToDictionary(i => i.Key, i => i.Value));
}
}
private static string[] EmptyStringArray = new string[0];
private static string[][] Empty2DStringArray = new string[0][];
[Obsolete("Not accurate, eg 音乐 will not return yinyue but returns yinle ")]
/// <summary>
/// replace chinese character with pinyin, non chinese character won't be modified
/// <param name="word"> should be word or sentence, instead of single character. e.g. 微软 </param>
/// </summary>
public string[] Pinyin(string word)
{
if (!_settings.ShouldUsePinyin)
{
return EmptyStringArray;
}
var pinyin = word.Select(c =>
{
var pinyins = PinyinHelper.toHanyuPinyinStringArray(c);
var result = pinyins == null ? c.ToString() : pinyins[0];
return result;
}).ToArray();
return pinyin;
}
/// <summmary>
/// replace chinese character with pinyin, non chinese character won't be modified
/// Because we don't have words dictionary, so we can only return all possiblie pinyin combination
/// e.g. 音乐 will return yinyue and yinle
/// <param name="characters"> should be word or sentence, instead of single character. e.g. 微软 </param>
/// </summmary>
public string[][] PinyinCombination(string characters)
{
if (!_settings.ShouldUsePinyin || string.IsNullOrEmpty(characters))
{
return Empty2DStringArray;
}
if (!PinyinCache.ContainsKey(characters))
{
var allPinyins = new List<string[]>();
foreach (var c in characters)
{
var pinyins = PinyinHelper.toHanyuPinyinStringArray(c, Format);
if (pinyins != null)
{
var r = pinyins.Distinct().ToArray();
allPinyins.Add(r);
}
else
{
var r = new[] { c.ToString() };
allPinyins.Add(r);
}
}
var combination = allPinyins.Aggregate(Combination).Select(c => c.Split(';')).ToArray();
PinyinCache[characters] = combination;
return combination;
}
else
{
return PinyinCache[characters];
}
}
public string Acronym(string[] pinyin)
{
var acronym = string.Join("", pinyin.Select(p => p[0]));
return acronym;
}
public bool ContainsChinese(string word)
{
if (!_settings.ShouldUsePinyin)
{
return false;
}
if (word.Length > 40)
{
//Skip strings that are too long string for Pinyin conversion.
return false;
}
var chinese = word.Select(PinyinHelper.toHanyuPinyinStringArray)
.Any(p => p != null);
return chinese;
}
private string[] Combination(string[] array1, string[] array2)
{
if (!_settings.ShouldUsePinyin)
{
return EmptyStringArray;
}
var combination = (
from a1 in array1
from a2 in array2
select $"{a1};{a2}"
).ToArray();
return combination;
}
}
}

View file

@ -23,8 +23,10 @@ namespace Flow.Launcher.Infrastructure
public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location.NonNull()).ProductVersion;
public static readonly int ThumbnailSize = 64;
public static readonly string DefaultIcon = Path.Combine(ProgramDirectory, "Images", "app.png");
public static readonly string ErrorIcon = Path.Combine(ProgramDirectory, "Images", "app_error.png");
private static readonly string ImagesDirectory = Path.Combine(ProgramDirectory, "Images");
public static readonly string DefaultIcon = Path.Combine(ImagesDirectory, "app.png");
public static readonly string ErrorIcon = Path.Combine(ImagesDirectory, "app_error.png");
public static readonly string MissingImgIcon = Path.Combine(ImagesDirectory, "app_missing_img.png");
public static string PythonPath;

View file

@ -53,10 +53,10 @@
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog.Schema" Version="4.7.0-rc1" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.9.0" />
<PackageReference Include="Pinyin4DotNet" Version="2016.4.23.4" />
<PackageReference Include="System.Drawing.Common" Version="4.7.0" />
<PackageReference Include="System.Runtime" Version="4.3.1" />
<PackageReference Include="PropertyChanged.Fody" Version="2.5.13" />
<PackageReference Include="ToolGood.Words.Pinyin" Version="3.0.1.4" />
</ItemGroup>
<ItemGroup>

View file

@ -1,32 +0,0 @@
using System;
namespace Flow.Launcher.Infrastructure
{
[Obsolete("This class is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
public class FuzzyMatcher
{
private string query;
private MatchOption opt;
private FuzzyMatcher(string query, MatchOption opt)
{
this.query = query.Trim();
this.opt = opt;
}
public static FuzzyMatcher Create(string query)
{
return new FuzzyMatcher(query, new MatchOption());
}
public static FuzzyMatcher Create(string query, MatchOption opt)
{
return new FuzzyMatcher(query, opt);
}
public MatchResult Evaluate(string str)
{
return StringMatcher.Instance.FuzzyMatch(query, str, opt);
}
}
}

View file

@ -37,7 +37,7 @@ namespace Flow.Launcher.Infrastructure.Image
ImageCache.Usage = LoadStorageToConcurrentDictionary();
foreach (var icon in new[] { Constant.DefaultIcon, Constant.ErrorIcon })
foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon })
{
ImageSource img = new BitmapImage(new Uri(icon));
img.Freeze();
@ -106,7 +106,7 @@ namespace Flow.Launcher.Infrastructure.Image
{
if (string.IsNullOrEmpty(path))
{
return new ImageResult(ImageCache[Constant.ErrorIcon], ImageType.Error);
return new ImageResult(_imageCache[Constant.MissingImgIcon], ImageType.Error);
}
if (ImageCache.ContainsKey(path))
{
@ -139,8 +139,8 @@ namespace Flow.Launcher.Infrastructure.Image
Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on first try", e);
Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path} on second try", e2);
ImageSource image = ImageCache[Constant.ErrorIcon];
ImageCache[path] = image;
ImageSource image = _imageCache[Constant.MissingImgIcon];
_imageCache[path] = image;
imageResult = new ImageResult(image, ImageType.Error);
}
}
@ -191,8 +191,8 @@ namespace Flow.Launcher.Infrastructure.Image
}
else
{
image = ImageCache[Constant.ErrorIcon];
path = Constant.ErrorIcon;
image = _imageCache[Constant.MissingImgIcon];
path = Constant.MissingImgIcon;
}
if (type != ImageType.Error)

View file

@ -0,0 +1,65 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using ToolGood.Words.Pinyin;
using System.Threading.Tasks;
namespace Flow.Launcher.Infrastructure
{
public interface IAlphabet
{
string Translate(string stringToTranslate);
}
public class PinyinAlphabet : IAlphabet
{
private ConcurrentDictionary<string, string> _pinyinCache = new ConcurrentDictionary<string, string>();
private Settings _settings;
public void Initialize([NotNull] Settings settings)
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
}
public string Translate(string content)
{
if (_settings.ShouldUsePinyin)
{
if (!_pinyinCache.ContainsKey(content))
{
if (WordsHelper.HasChinese(content))
{
var result = WordsHelper.GetPinyin(content, ";");
result = GetFirstPinyinChar(result) + result.Replace(";", "");
_pinyinCache[content] = result;
return result;
}
else
{
return content;
}
}
else
{
return _pinyinCache[content];
}
}
else
{
return content;
}
}
private string GetFirstPinyinChar(string content)
{
return string.Concat(content.Split(';').Select(x => x.First()));
}
}
}

View file

@ -21,18 +21,6 @@ namespace Flow.Launcher.Infrastructure
public static StringMatcher Instance { get; internal set; }
[Obsolete("This method is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
public static int Score(string source, string target)
{
return FuzzySearch(target, source).Score;
}
[Obsolete("This method is obsolete and should not be used. Please use the static function StringMatcher.FuzzySearch")]
public static bool IsMatch(string source, string target)
{
return Score(source, target) > 0;
}
public static MatchResult FuzzySearch(string query, string stringToCompare)
{
return Instance.FuzzyMatch(query, stringToCompare);
@ -323,18 +311,6 @@ namespace Flow.Launcher.Infrastructure
public class MatchOption
{
/// <summary>
/// prefix of match char, use for highlight
/// </summary>
[Obsolete("this is never used")]
public string Prefix { get; set; } = "";
/// <summary>
/// suffix of match char, use for highlight
/// </summary>
[Obsolete("this is never used")]
public string Suffix { get; set; } = "";
public bool IgnoreCase { get; set; } = true;
}
}

View file

@ -30,5 +30,6 @@ 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);
}
}

View file

@ -15,6 +15,16 @@ namespace Flow.Launcher.Infrastructure.UserSettings
if (Plugins.ContainsKey(metadata.ID))
{
var settings = Plugins[metadata.ID];
// TODO: Remove. This is one off for 1.2.0 release.
// Introduced a new action keyword in Explorer, so need to update plugin setting in the UserData folder.
// This kind of plugin meta update should be handled by a dedicated method trigger by version bump.
if (metadata.ID == "572be03c74c642baae319fc283e561a8" && metadata.ActionKeywords.Count != settings.ActionKeywords.Count)
settings.ActionKeywords = metadata.ActionKeywords;
if (string.IsNullOrEmpty(settings.Version))
settings.Version = metadata.Version;
if (settings.ActionKeywords?.Count > 0)
{
metadata.ActionKeywords = settings.ActionKeywords;
@ -28,6 +38,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
ID = metadata.ID,
Name = metadata.Name,
Version = metadata.Version,
ActionKeywords = metadata.ActionKeywords,
Disabled = metadata.Disabled
};
@ -39,6 +50,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{
public string ID { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public List<string> ActionKeywords { get; set; } // a reference of the action keywords from plugin manager
/// <summary>

View file

@ -70,12 +70,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings
public PluginsSettings PluginSettings { get; set; } = new PluginsSettings();
public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();
[Obsolete]
public double Opacity { get; set; } = 1;
[Obsolete]
public OpacityMode OpacityMode { get; set; } = OpacityMode.Normal;
public bool DontPromptUpdateMsg { get; set; }
public bool EnableUpdateLog { get; set; }
@ -108,12 +102,4 @@ namespace Flow.Launcher.Infrastructure.UserSettings
Empty,
Preserved
}
[Obsolete]
public enum OpacityMode
{
Normal = 0,
LayeredWindow = 1,
DWM = 2
}
}

View file

@ -11,23 +11,6 @@ namespace Flow.Launcher.Plugin
List<Result> LoadContextMenus(Result selectedResult);
}
[Obsolete("If a plugin has a action keyword, then it is exclusive. This interface will be remove in v1.3.0")]
public interface IExclusiveQuery : IFeatures
{
[Obsolete("If a plugin has a action keyword, then it is exclusive. This method will be remove in v1.3.0")]
bool IsExclusiveQuery(Query query);
}
/// <summary>
/// Represent plugin query will be executed in UI thread directly. Don't do long-running operation in Query method if you implement this interface
/// <remarks>This will improve the performance of instant search like websearch or cmd plugin</remarks>
/// </summary>
[Obsolete("Flow Launcher is fast enough now, executed on ui thread is no longer needed")]
public interface IInstantQuery : IFeatures
{
bool IsInstantQuery(string query);
}
/// <summary>
/// Represent plugins that support internationalization
/// </summary>

View file

@ -1,9 +0,0 @@
using System;
namespace Flow.Launcher.Plugin.Features
{
[Obsolete("Delete Flow.Launcher.Plugin.Features using directive, " +
"and use Flow.Launcher.Plugin.Feature.IContextMenu instead, " +
"this method will be removed in v1.3.0")]
public interface IContextMenu { }
}

View file

@ -1,9 +0,0 @@
using System;
namespace Flow.Launcher.Plugin.Features
{
[Obsolete("Delete Flow.Launcher.Plugin.Features using directive, " +
"and use Flow.Launcher.Plugin.Feature.IInstantQuery instead, " +
"this method will be removed in v1.3.0")]
public interface IExclusiveQuery { }
}

View file

@ -1,9 +0,0 @@
using System;
namespace Flow.Launcher.Plugin.Features
{
[Obsolete("Delete Flow.Launcher.Plugin.Features using directive, " +
"and use Flow.Launcher.Plugin.Feature.IInstantQuery instead, " +
"this method will be removed in v1.3.0")]
public interface IInstantQuery { }
}

View file

@ -14,10 +14,10 @@
</PropertyGroup>
<PropertyGroup>
<Version>1.1.0</Version>
<PackageVersion>1.1.0</PackageVersion>
<AssemblyVersion>1.1.0</AssemblyVersion>
<FileVersion>1.1.0</FileVersion>
<Version>1.2.1</Version>
<PackageVersion>1.2.1</PackageVersion>
<AssemblyVersion>1.2.1</AssemblyVersion>
<FileVersion>1.2.1</FileVersion>
<PackageId>Flow.Launcher.Plugin</PackageId>
<Authors>Flow-Launcher</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>

View file

@ -8,15 +8,6 @@ namespace Flow.Launcher.Plugin
/// </summary>
public interface IPublicAPI
{
/// <summary>
/// Push result to query box
/// </summary>
/// <param name="query"></param>
/// <param name="plugin"></param>
/// <param name="results"></param>
[Obsolete("This method will be removed in Flow Launcher 1.3")]
void PushResults(Query query, PluginMetadata plugin, List<Result> results);
/// <summary>
/// Change Flow.Launcher query
/// </summary>
@ -27,42 +18,11 @@ namespace Flow.Launcher.Plugin
/// </param>
void ChangeQuery(string query, bool requery = false);
/// <summary>
/// Just change the query text, this won't raise search
/// </summary>
/// <param name="query"></param>
[Obsolete]
void ChangeQueryText(string query, bool selectAll = false);
/// <summary>
/// Close Flow Launcher
/// </summary>
[Obsolete]
void CloseApp();
/// <summary>
/// Restart Flow Launcher
/// </summary>
void RestartApp();
/// <summary>
/// Restart Flow Launcher
/// </summary>
[Obsolete("Use RestartApp instead. This method will be removed in Flow Launcher 1.3")]
void RestarApp();
/// <summary>
/// Hide Flow Launcher
/// </summary>
[Obsolete]
void HideApp();
/// <summary>
/// Show Flow Launcher
/// </summary>
[Obsolete]
void ShowApp();
/// <summary>
/// Save all Flow Launcher settings
/// </summary>
@ -103,18 +63,6 @@ namespace Flow.Launcher.Plugin
/// </summary>
void OpenSettingDialog();
/// <summary>
/// Show loading animation
/// </summary>
[Obsolete("automatically start")]
void StartLoadingBar();
/// <summary>
/// Stop loading animation
/// </summary>
[Obsolete("automatically stop")]
void StopLoadingBar();
/// <summary>
/// Install Flow Launcher plugin
/// </summary>

View file

@ -43,9 +43,6 @@ namespace Flow.Launcher.Plugin
return Name;
}
[Obsolete("Use IcoPath")]
public string FullIcoPath => IcoPath;
/// <summary>
/// Init time include both plugin load time and init time
/// </summary>

View file

@ -94,14 +94,5 @@ namespace Flow.Launcher.Plugin
}
public override string ToString() => RawQuery;
[Obsolete("Use ActionKeyword, this property will be removed in v1.3.0")]
public string ActionName { get; internal set; }
[Obsolete("Use Search instead, this property will be removed in v1.3.0")]
public List<string> ActionParameters { get; internal set; }
[Obsolete("Use Search instead, this method will be removed in v1.3.0")]
public string GetAllRemainingParameter() => Search;
}
}

View file

@ -104,21 +104,6 @@ namespace Flow.Launcher.Plugin
return Title + SubTitle;
}
/// <summary>
/// Context menus associate with this result
/// </summary>
[Obsolete("Use IContextMenu instead")]
public List<Result> ContextMenu { get; set; }
[Obsolete("Use Object initializer instead")]
public Result(string Title, string IcoPath, string SubTitle = null)
{
this.Title = Title;
this.IcoPath = IcoPath;
this.SubTitle = SubTitle;
}
public Result() { }
/// <summary>

View file

@ -11,7 +11,13 @@ namespace Flow.Launcher.Plugin.SharedCommands
private const string FileExplorerProgramEXE = "explorer.exe";
public static void Copy(this string sourcePath, string targetPath)
/// <summary>
/// Copies the folder and all of its files and folders
/// including subfolders to the target location
/// </summary>
/// <param name="sourcePath"></param>
/// <param name="targetPath"></param>
public static void CopyAll(this string sourcePath, string targetPath)
{
// Get the subdirectories for the specified directory.
DirectoryInfo dir = new DirectoryInfo(sourcePath);
@ -44,7 +50,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(targetPath, subdir.Name);
Copy(subdir.FullName, temppath);
CopyAll(subdir.FullName, temppath);
}
}
catch (Exception e)
@ -108,7 +114,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
return Directory.Exists(path);
}
public static bool FileExits(this string filePath)
public static bool FileExists(this string filePath)
{
return File.Exists(filePath);
}
@ -118,7 +124,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
var psi = new ProcessStartInfo { FileName = FileExplorerProgramName, UseShellExecute = true, Arguments = fileOrFolderPath };
try
{
if (LocationExists(fileOrFolderPath) || FileExits(fileOrFolderPath))
if (LocationExists(fileOrFolderPath) || FileExists(fileOrFolderPath))
Process.Start(psi);
}
catch (Exception e)
@ -172,7 +178,7 @@ namespace Flow.Launcher.Plugin.SharedCommands
///<summary>
/// Gets the previous level directory from a path string.
/// Checks that previous level directory exists and returns it
/// as a path string, or empty string if doesn't exit
/// as a path string, or empty string if doesn't exist
///</summary>
public static string GetPreviousExistingDirectory(Func<string, bool> locationExists, string path)
{

View file

@ -58,8 +58,8 @@ namespace Flow.Launcher.Test.Plugins
$"Actual: {result}{Environment.NewLine}");
}
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType FROM SystemIndex WHERE directory='file:C:\\'")]
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\'")]
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\'")]
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\'")]
public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString)
{
// Given
@ -74,7 +74,7 @@ namespace Flow.Launcher.Test.Plugins
$"Actual string was: {queryString}{Environment.NewLine}");
}
[TestCase("C:\\SomeFolder\\flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemPathDisplay, System.ItemType " +
[TestCase("C:\\SomeFolder\\flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
"FROM SystemIndex WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033))" +
" AND directory='file:C:\\SomeFolder'")]
@ -126,7 +126,7 @@ namespace Flow.Launcher.Test.Plugins
$"Actual string was: {resultString}{Environment.NewLine}");
}
[TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemPathDisplay\", \"System.ItemType\" " +
[TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" " +
"FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:'")]
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
@ -184,6 +184,55 @@ namespace Flow.Launcher.Test.Plugins
$"Actual number of results is {results.Count} {Environment.NewLine}");
}
[TestCase(@"some words", @"FREETEXT('some words')")]
public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSearch_ThenShouldReturnFreeTextString(
string querySearchString, string expectedString)
{
// Given
var queryConstructor = new QueryConstructor(new Settings());
//When
var resultString = queryConstructor.QueryWhereRestrictionsForFileContentSearch(querySearchString);
// Then
Assert.IsTrue(resultString == expectedString,
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
$"Actual string was: {resultString}{Environment.NewLine}");
}
[TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:'")]
public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString(
string userSearchString, string expectedString)
{
// Given
var queryConstructor = new QueryConstructor(new Settings());
//When
var resultString = queryConstructor.QueryForFileContentSearch(userSearchString);
// Then
Assert.IsTrue(resultString == expectedString,
$"Expected query string: {expectedString}{Environment.NewLine} " +
$"Actual string was: {resultString}{Environment.NewLine}");
}
public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileContentSearchRequiredShouldReturnTrue()
{
// Given
var query = new Query { ActionKeyword = "doc:", Search = "search term" };
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
// When
var result = searchManager.IsFileContentSearch(query.ActionKeyword);
// Then
Assert.IsTrue(result,
$"Expected True for file content search. {Environment.NewLine} " +
$"Actual result was: {result}{Environment.NewLine}");
}
[TestCase(@"c:\\", false)]
[TestCase(@"i:\", true)]
[TestCase(@"\c:\", false)]

View file

@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
@ -28,7 +29,7 @@ namespace Flow.Launcher
private SettingWindowViewModel _settingsVM;
private readonly Updater _updater = new Updater(Flow.Launcher.Properties.Settings.Default.GithubRepo);
private readonly Portable _portable = new Portable();
private readonly Alphabet _alphabet = new Alphabet();
private readonly PinyinAlphabet _alphabet = new PinyinAlphabet();
private StringMatcher _stringMatcher;
[STAThread]
@ -85,6 +86,8 @@ namespace Flow.Launcher
Http.Proxy = _settings.Proxy;
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
RegisterExitEvents();
AutoStartup();

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -111,7 +111,7 @@
<system:String x:Key="done">Done</system:String>
<system:String x:Key="cannotFindSpecifiedPlugin">Can't find specified plugin</system:String>
<system:String x:Key="newActionKeywordsCannotBeEmpty">New Action Keyword can't be empty</system:String>
<system:String x:Key="newActionKeywordsHasBeenAssigned">New Action Keywords have been assigned to another plugin, please assign other new action keyword</system:String>
<system:String x:Key="newActionKeywordsHasBeenAssigned">This new Action Keyword is already assigned to another plugin, please choose a different one</system:String>
<system:String x:Key="success">Success</system:String>
<system:String x:Key="actionkeyword_tips">Use * if you don't want to specify an action keyword</system:String>

View file

@ -21,11 +21,11 @@ namespace Flow.Launcher
{
private readonly SettingWindowViewModel _settingsVM;
private readonly MainViewModel _mainVM;
private readonly Alphabet _alphabet;
private readonly PinyinAlphabet _alphabet;
#region Constructor
public PublicAPIInstance(SettingWindowViewModel settingsVM, MainViewModel mainVM, Alphabet alphabet)
public PublicAPIInstance(SettingWindowViewModel settingsVM, MainViewModel mainVM, PinyinAlphabet alphabet)
{
_settingsVM = settingsVM;
_mainVM = mainVM;
@ -48,12 +48,6 @@ namespace Flow.Launcher
_mainVM.ChangeQueryText(query);
}
[Obsolete]
public void CloseApp()
{
Application.Current.MainWindow.Close();
}
public void RestartApp()
{
_mainVM.MainWindowVisibility = Visibility.Hidden;
@ -82,7 +76,6 @@ namespace Flow.Launcher
_settingsVM.Save();
PluginManager.Save();
ImageLoader.Save();
_alphabet.Save();
}
public void ReloadAllPluginData()
@ -90,18 +83,6 @@ namespace Flow.Launcher
PluginManager.ReloadData();
}
[Obsolete]
public void HideApp()
{
_mainVM.MainWindowVisibility = Visibility.Hidden;
}
[Obsolete]
public void ShowApp()
{
_mainVM.MainWindowVisibility = Visibility.Visible;
}
public void ShowMsg(string title, string subTitle = "", string iconPath = "")
{
ShowMsg(title, subTitle, iconPath, true);
@ -151,21 +132,6 @@ namespace Flow.Launcher
public event FlowLauncherGlobalKeyboardEventHandler GlobalKeyboardEvent;
[Obsolete("This will be removed in Flow Launcher 1.3")]
public void PushResults(Query query, PluginMetadata plugin, List<Result> results)
{
results.ForEach(o =>
{
o.PluginDirectory = plugin.PluginDirectory;
o.PluginID = plugin.ID;
o.OriginQuery = query;
});
Task.Run(() =>
{
_mainVM.UpdateResultView(results, plugin, query);
});
}
#endregion
#region Private Methods

View file

@ -51,7 +51,7 @@ namespace Flow.Launcher.ViewModel
catch (Exception e)
{
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
imagePath = Constant.ErrorIcon;
imagePath = Constant.MissingImgIcon;
}
}

View file

@ -19,10 +19,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
set
{
m_Name = value;
PinyinName = m_Name.Unidecode();
}
}
public string PinyinName { get; private set; }
public string Url { get; set; }
public string Source { get; set; }
public int Score { get; set; }

View file

@ -6,19 +6,19 @@ namespace Flow.Launcher.Plugin.BrowserBookmark.Commands
{
internal static class Bookmarks
{
internal static bool MatchProgram(Bookmark bookmark, string queryString)
internal static MatchResult MatchProgram(Bookmark bookmark, string queryString)
{
if (StringMatcher.FuzzySearch(queryString, bookmark.Name).IsSearchPrecisionScoreMet()) return true;
if (StringMatcher.FuzzySearch(queryString, bookmark.PinyinName).IsSearchPrecisionScoreMet()) return true;
if (StringMatcher.FuzzySearch(queryString, bookmark.Url).IsSearchPrecisionScoreMet()) return true;
var match = StringMatcher.FuzzySearch(queryString, bookmark.Name);
if (match.IsSearchPrecisionScoreMet())
return match;
return false;
return StringMatcher.FuzzySearch(queryString, bookmark.Url);
}
internal static List<Bookmark> LoadAllBookmarks()
{
var allbookmarks = new List<Bookmark>();
var chromeBookmarks = new ChromeBookmarks();
var mozBookmarks = new FirefoxBookmarks();
var edgeBookmarks = new EdgeBookmarks();

View file

@ -18,7 +18,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
return bookmarks;
}
private void ParseEdgeBookmarks(String path, string source)
private void ParseEdgeBookmarks(string path, string source)
{
if (!File.Exists(path)) return;
@ -72,12 +72,13 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
private void LoadEdgeBookmarks()
{
String platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
LoadEdgeBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge");
LoadEdgeBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev");
LoadEdgeBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary");
}
private String DecodeUnicode(String dataStr)
private string DecodeUnicode(string dataStr)
{
Regex reg = new Regex(@"(?i)\\[uU]([0-9a-f]{4})");
return reg.Replace(dataStr, m => ((char)Convert.ToInt32(m.Groups[1].Value, 16)).ToString());

View file

@ -12,7 +12,7 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, ISavable
{
private PluginInitContext context;
private List<Bookmark> cachedBookmarks = new List<Bookmark>();
private readonly Settings _settings;
@ -33,40 +33,60 @@ namespace Flow.Launcher.Plugin.BrowserBookmark
public List<Result> Query(Query query)
{
string param = query.GetAllRemainingParameter().TrimStart();
string param = query.Search.TrimStart();
// Should top results be returned? (true if no search parameters have been passed)
var topResults = string.IsNullOrEmpty(param);
var returnList = cachedBookmarks;
if (!topResults)
{
// Since we mixed chrome and firefox bookmarks, we should order them again
returnList = cachedBookmarks.Where(o => Bookmarks.MatchProgram(o, param)).ToList();
returnList = returnList.OrderByDescending(o => o.Score).ToList();
}
return returnList.Select(c => new Result()
{
Title = c.Name,
SubTitle = c.Url,
IcoPath = @"Images\bookmark.png",
Score = 5,
Action = (e) =>
var returnList = cachedBookmarks.Select(c => new Result()
{
if (_settings.OpenInNewBrowserWindow)
Title = c.Name,
SubTitle = c.Url,
IcoPath = @"Images\bookmark.png",
Score = Bookmarks.MatchProgram(c, param).Score,
Action = _ =>
{
c.Url.NewBrowserWindow(_settings.BrowserPath);
}
else
{
c.Url.NewTabInBrowser(_settings.BrowserPath);
}
if (_settings.OpenInNewBrowserWindow)
{
c.Url.NewBrowserWindow(_settings.BrowserPath);
}
else
{
c.Url.NewTabInBrowser(_settings.BrowserPath);
}
return true;
}
}).ToList();
return true;
}
}).Where(r => r.Score > 0);
return returnList.ToList();
}
else
{
return cachedBookmarks.Select(c => new Result()
{
Title = c.Name,
SubTitle = c.Url,
IcoPath = @"Images\bookmark.png",
Score = 5,
Action = _ =>
{
if (_settings.OpenInNewBrowserWindow)
{
c.Url.NewBrowserWindow(_settings.BrowserPath);
}
else
{
c.Url.NewTabInBrowser(_settings.BrowserPath);
}
return true;
}
}).ToList();
}
}
public void ReloadData()

View file

@ -4,7 +4,7 @@
"Name": "Browser Bookmarks",
"Description": "Search your browser bookmarks",
"Author": "qianlifeng, Ioannis G.",
"Version": "1.2.0",
"Version": "1.2.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.browserBookmark.dll",

View file

@ -34,7 +34,7 @@ namespace Flow.Launcher.Plugin.Caculator
{
MagesEngine = new Engine();
}
public void Init(PluginInitContext context)
{
Context = context;
@ -78,16 +78,16 @@ namespace Flow.Launcher.Plugin.Caculator
{
try
{
Clipboard.SetText(newResult);
Clipboard.SetDataObject(newResult);
return true;
}
catch (ExternalException)
catch (ExternalException e)
{
MessageBox.Show("Copy failed, please try later");
return false;
}
}
}
}
};
}
}
@ -111,7 +111,7 @@ namespace Flow.Launcher.Plugin.Caculator
{
return false;
}
if (!IsBracketComplete(query.Search))
{
return false;
@ -164,7 +164,7 @@ namespace Flow.Launcher.Plugin.Caculator
return leftBracketCount == 0;
}
public string GetTranslatedPluginTitle()
{
return Context.API.GetTranslation("flowlauncher_plugin_caculator_plugin_name");

View file

@ -4,7 +4,7 @@
"Name": "Calculator",
"Description": "Provide mathematical calculations.(Try 5*3-2 in Flow Launcher)",
"Author": "cxfksword",
"Version": "1.0.0",
"Version": "1.0.1",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Caculator.dll",

@ -0,0 +1 @@
Subproject commit 6d5b687e240a6abdc5623d8a8e09501f3994b0d3

View file

@ -104,6 +104,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Flow.Launcher.Core\Flow.Launcher.Core.csproj" />
<ProjectReference Include="..\..\Flow.Launcher.Infrastructure\Flow.Launcher.Infrastructure.csproj" />
<ProjectReference Include="..\..\Flow.Launcher.Plugin\Flow.Launcher.Plugin.csproj" />
</ItemGroup>

View file

@ -1,22 +1,26 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<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">
<!--Dialogues-->
<system:String x:Key="plugin_explorer_make_selection_warning">Please make a selection first</system:String>
<system:String x:Key="plugin_explorer_select_folder_link_warning">Please select a folder link</system:String>
<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_deletefilefolderconfirm">Are you sure you want to permanently delete this {0}?</system:String>
<system:String x:Key="plugin_explorer_deletefilefoldersuccess">Deletion successful</system:String>
<system:String x:Key="plugin_explorer_deletefilefoldersuccess_detail">Successfully deleted the {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>
<!--Controls-->
<system:String x:Key="plugin_explorer_delete">Delete</system:String>
<system:String x:Key="plugin_explorer_edit">Edit</system:String>
<system:String x:Key="plugin_explorer_add">Add</system:String>
<system:String x:Key="plugin_explorer_manageactionkeywords_header">Customise Action Keywords</system:String>
<system:String x:Key="plugin_explorer_quickfolderaccess_header">Quick Folder Access Paths</system:String>
<system:String x:Key="plugin_explorer_indexsearchexcludedpaths_header">Index Search Excluded Paths</system:String>
<system:String x:Key="plugin_explorer_manageindexoptions">Indexing Options</system:String>
<system:String x:Key="plugin_explorer_actionkeywordview_search">Search Activation:</system:String>
<system:String x:Key="plugin_explorer_actionkeywordview_filecontentsearch">File Content Search:</system:String>
<!--Plugin Infos-->
<system:String x:Key="plugin_explorer_plugin_name">Explorer</system:String>

View file

@ -22,6 +22,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal const char AllFilesFolderSearchWildcard = '>';
internal const string DefaultContentSearchActionKeyword = "doc:";
internal const char DirectorySeperator = '\\';
internal const string WindowsIndexingOptions = "srchadmin.dll";

View file

@ -22,11 +22,20 @@ namespace Flow.Launcher.Plugin.Explorer.Search
foreach (DictionaryEntry special in Environment.GetEnvironmentVariables())
{
if (Directory.Exists(special.Value.ToString()))
var path = special.Value.ToString();
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(), special.Value.ToString());
envStringPaths.Add(special.Key.ToString().ToLower(), path);
}
}

View file

@ -6,14 +6,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
{
public class QuickFolderAccess
{
internal List<Result> FolderList(Query query, List<FolderLink> folderLinks, PluginInitContext context)
internal List<Result> FolderListMatched(Query query, List<FolderLink> folderLinks, PluginInitContext context)
{
if (string.IsNullOrEmpty(query.Search))
return folderLinks
.Select(item =>
new ResultManager(context)
.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
.ToList();
return new List<Result>();
string search = query.Search.ToLower();
@ -24,5 +20,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.FolderLinks
.CreateFolderResult(item.Nickname, item.Path, item.Path, query))
.ToList();
}
internal List<Result> FolderListAll(Query query, List<FolderLink> folderLinks, PluginInitContext context)
=> folderLinks
.Select(item =>
new ResultManager(context).CreateFolderResult(item.Nickname, item.Path, item.Path, query))
.ToList();
}
}

View file

@ -34,13 +34,19 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var querySearch = query.Search;
var quickFolderLinks = quickFolderAccess.FolderList(query, settings.QuickFolderAccessLinks, context);
if (IsFileContentSearch(query.ActionKeyword))
return WindowsIndexFileContentSearch(query, querySearch);
// This allows the user to type the assigned action keyword and only see the list of quick folder links
if (settings.QuickFolderAccessLinks.Count > 0
&& query.ActionKeyword == settings.SearchActionKeyword
&& string.IsNullOrEmpty(query.Search))
return quickFolderAccess.FolderListAll(query, settings.QuickFolderAccessLinks, context);
var quickFolderLinks = quickFolderAccess.FolderListMatched(query, settings.QuickFolderAccessLinks, context);
if (quickFolderLinks.Count > 0)
return quickFolderLinks;
if (string.IsNullOrEmpty(querySearch))
return results;
results.AddRange(quickFolderLinks);
var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch);
@ -51,7 +57,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search
var isEnvironmentVariablePath = querySearch.Substring(1).Contains("%\\");
if (!FilesFolders.IsLocationPathString(querySearch) && !isEnvironmentVariablePath)
return WindowsIndexFilesAndFoldersSearch(query, querySearch);
{
results.AddRange(WindowsIndexFilesAndFoldersSearch(query, querySearch));
return results;
}
var locationPath = querySearch;
@ -74,6 +84,24 @@ namespace Flow.Launcher.Plugin.Explorer.Search
return results;
}
private List<Result> WindowsIndexFileContentSearch(Query query, string querySearchString)
{
var queryConstructor = new QueryConstructor(settings);
if (string.IsNullOrEmpty(querySearchString))
return new List<Result>();
return indexSearch.WindowsIndexSearch(querySearchString,
queryConstructor.CreateQueryHelper().ConnectionString,
queryConstructor.QueryForFileContentSearch,
query);
}
public bool IsFileContentSearch(string actionKeyword)
{
return actionKeyword == settings.FileContentSearchActionKeyword;
}
private List<Result> DirectoryInfoClassSearch(Query query, string querySearch)
{
var directoryInfoSearch = new DirectoryInfoSearch(context);
@ -116,15 +144,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search
private bool UseWindowsIndexForDirectorySearch(string locationPath)
{
var pathToDirectory = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath);
if (!settings.UseWindowsIndexForDirectorySearch)
return false;
if (settings.IndexSearchExcludedSubdirectoryPaths
.Any(x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath)
.Any(x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory)
.StartsWith(x.Path, StringComparison.OrdinalIgnoreCase)))
return false;
return indexSearch.PathIsIndexed(locationPath);
return indexSearch.PathIsIndexed(pathToDirectory);
}
}
}

View file

@ -21,7 +21,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
private readonly ResultManager resultManager;
// Reserved keywords in oleDB
private readonly string reservedStringPattern = @"^[\/\\\$\%]+$";
private readonly string reservedStringPattern = @"^[\/\\\$\%_]+$";
internal IndexSearch(PluginInitContext context)
{
@ -51,17 +51,24 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
{
if (dataReaderResults.GetValue(0) != DBNull.Value && dataReaderResults.GetValue(1) != DBNull.Value)
{
// # is URI syntax for the fragment component, need to be encoded so LocalPath returns complete path
var encodedFragmentPath = dataReaderResults
.GetString(1)
.Replace("#", "%23", StringComparison.OrdinalIgnoreCase);
var path = new Uri(encodedFragmentPath).LocalPath;
if (dataReaderResults.GetString(2) == "Directory")
{
folderResults.Add(resultManager.CreateFolderResult(
dataReaderResults.GetString(0),
dataReaderResults.GetString(1),
dataReaderResults.GetString(1),
path,
path,
query, true, true));
}
else
{
fileResults.Add(resultManager.CreateFileResult(dataReaderResults.GetString(1), query, true, true));
fileResults.Add(resultManager.CreateFileResult(path, query, true, true));
}
}
}

View file

@ -21,7 +21,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
baseQuery.QueryMaxResults = settings.MaxResult;
// Set list of columns we want to display, getting the path presently
baseQuery.QuerySelectColumns = "System.FileName, System.ItemPathDisplay, System.ItemType";
baseQuery.QuerySelectColumns = "System.FileName, System.ItemUrl, System.ItemType";
// Filter based on file name
baseQuery.QueryContentProperties = "System.FileName";
@ -117,5 +117,23 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
{
return $"scope='file:'";
}
///<summary>
/// Search will be performed on all indexed file contents for the specified search keywords.
///</summary>
public string QueryForFileContentSearch(string userSearchString)
{
string query = "SELECT TOP " + settings.MaxResult + $" {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE ";
return query + QueryWhereRestrictionsForFileContentSearch(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch();
}
///<summary>
/// Set the required WHERE clause restriction to search within file content.
///</summary>
public string QueryWhereRestrictionsForFileContentSearch(string searchQuery)
{
return $"FREETEXT('{searchQuery}')";
}
}
}

View file

@ -1,4 +1,5 @@
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
using Newtonsoft.Json;
using System.Collections.Generic;
@ -17,5 +18,11 @@ namespace Flow.Launcher.Plugin.Explorer
[JsonProperty]
public List<FolderLink> IndexSearchExcludedSubdirectoryPaths { get; set; } = new List<FolderLink>();
[JsonProperty]
public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign;
[JsonProperty]
public string FileContentSearchActionKeyword { get; set; } = Constants.DefaultContentSearchActionKeyword;
}
}

View file

@ -1,4 +1,5 @@
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin.Explorer.Search;
using Flow.Launcher.Plugin.Explorer.Search.FolderLinks;
using System.Diagnostics;
@ -40,5 +41,20 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels
Process.Start(psi);
}
internal void UpdateActionKeyword(string newActionKeyword, string oldActionKeyword)
{
PluginManager.ReplaceActionKeyword(Context.CurrentPluginMetadata.ID, oldActionKeyword, newActionKeyword);
if (Settings.FileContentSearchActionKeyword == oldActionKeyword)
Settings.FileContentSearchActionKeyword = newActionKeyword;
if (Settings.SearchActionKeyword == oldActionKeyword)
Settings.SearchActionKeyword = newActionKeyword;
}
internal bool IsActionKeywordAlreadyAssigned(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword);
internal bool IsNewActionKeywordGlobal(string newActionKeyword) => newActionKeyword == Query.GlobalPluginWildcardSign;
}
}

View file

@ -0,0 +1,36 @@
<Window x:Class="Flow.Launcher.Plugin.Explorer.Views.ActionKeywordSetting"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Flow.Launcher.Plugin.Explorer.Views"
mc:Ignorable="d"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
Title="Action Keyword Setting" Height="200" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Margin="20 10 10 10" FontSize="14" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"
HorizontalAlignment="Left" Text="Current Action Keyword:" />
<TextBox Name="txtCurrentActionKeyword"
Margin="10" Grid.Row="0" Width="105" Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Left" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="1" Grid.Column="1">
<Button Click="OnConfirmButtonClick"
Margin="10 0 10 0" Width="80" Height="35"
Content="OK" />
<Button Click="OnCancelButtonClick"
Margin="10 0 10 0" Width="80" Height="35"
Content="Cancel" />
</StackPanel>
</Grid>
</Window>

View file

@ -0,0 +1,85 @@
using Flow.Launcher.Plugin.Explorer.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Flow.Launcher.Plugin.Explorer.Views
{
/// <summary>
/// Interaction logic for ActionKeywordSetting.xaml
/// </summary>
public partial class ActionKeywordSetting : Window
{
private SettingsViewModel settingsViewModel;
private ActionKeywordView currentActionKeyword;
private List<ActionKeywordView> actionKeywordListView;
public ActionKeywordSetting(SettingsViewModel settingsViewModel, List<ActionKeywordView> actionKeywordListView, ActionKeywordView selectedActionKeyword)
{
InitializeComponent();
this.settingsViewModel = settingsViewModel;
currentActionKeyword = selectedActionKeyword;
txtCurrentActionKeyword.Text = selectedActionKeyword.Keyword;
this.actionKeywordListView = actionKeywordListView;
}
private void OnConfirmButtonClick(object sender, RoutedEventArgs e)
{
var newActionKeyword = txtCurrentActionKeyword.Text;
if (string.IsNullOrEmpty(newActionKeyword))
return;
if (newActionKeyword == currentActionKeyword.Keyword)
{
Close();
return;
}
if (settingsViewModel.IsNewActionKeywordGlobal(newActionKeyword)
&& currentActionKeyword.Description
== settingsViewModel.Context.API.GetTranslation("plugin_explorer_actionkeywordview_filecontentsearch"))
{
MessageBox.Show(settingsViewModel.Context.API.GetTranslation("plugin_explorer_globalActionKeywordInvalid"));
return;
}
if (!settingsViewModel.IsActionKeywordAlreadyAssigned(newActionKeyword))
{
settingsViewModel.UpdateActionKeyword(newActionKeyword, currentActionKeyword.Keyword);
actionKeywordListView.Where(x => x.Description == currentActionKeyword.Description).FirstOrDefault().Keyword = newActionKeyword;
Close();
return;
}
MessageBox.Show(settingsViewModel.Context.API.GetTranslation("newActionKeywordsHasBeenAssigned"));
}
private void OnCancelButtonClick(object sender, RoutedEventArgs e)
{
Close();
return;
}
}
}

View file

@ -1,4 +1,4 @@
<UserControl x:Class="Flow.Launcher.Plugin.Explorer.Views.ExplorerSettings"
<UserControl x:Class="Flow.Launcher.Plugin.Explorer.Views.ExplorerSettings"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@ -17,6 +17,16 @@
Text="{Binding Nickname, Mode=OneTime}"
Margin="0,5,0,5" />
</DataTemplate>
<DataTemplate x:Key="ListViewActionKeywords">
<Grid>
<TextBlock
Text="{Binding Description, Mode=OneTime}"
Margin="0,5,0,0" />
<TextBlock
Text="{Binding Keyword, Mode=OneTime}"
Margin="150,5,0,0" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
@ -25,8 +35,14 @@
</Grid.RowDefinitions>
<ScrollViewer Margin="20 35 0 0" HorizontalScrollBarVisibility="Hidden" Grid.Row="0" VerticalScrollBarVisibility="Auto">
<StackPanel>
<Expander Name="expActionKeywords" Header="{DynamicResource plugin_explorer_manageactionkeywords_header}"
Expanded="expActionKeywords_Click" Collapsed="expActionKeywords_Collapsed">
<ListView x:Name="lbxActionKeywords"
ItemTemplate="{StaticResource ListViewActionKeywords}"/>
</Expander>
<Expander Name="expFolderLinks" Header="{DynamicResource plugin_explorer_quickfolderaccess_header}"
Expanded="expFolderLinks_Click" Collapsed="expFolderLinks_Click">
Expanded="expFolderLinks_Click" Collapsed="expFolderLinks_Collapsed"
Margin="0 10 0 0">
<ListView
x:Name="lbxFolderLinks" AllowDrop="True"
Drop="lbxFolders_Drop"
@ -34,7 +50,7 @@
ItemTemplate="{StaticResource ListViewTemplateFolderLinks}"/>
</Expander>
<Expander x:Name="expExcludedPaths" Header="{DynamicResource plugin_explorer_indexsearchexcludedpaths_header}"
Expanded="expExcludedPaths_Click" Collapsed="expExcludedPaths_Click"
Expanded="expExcludedPaths_Click"
Margin="0 10 0 0">
<ListView
x:Name="lbxExcludedPaths" AllowDrop="True"

View file

@ -20,6 +20,9 @@ namespace Flow.Launcher.Plugin.Explorer.Views
public partial class ExplorerSettings
{
private readonly SettingsViewModel viewModel;
private List<ActionKeywordView> actionKeywordsListView;
public ExplorerSettings(SettingsViewModel viewModel)
{
InitializeComponent();
@ -30,6 +33,22 @@ namespace Flow.Launcher.Plugin.Explorer.Views
lbxExcludedPaths.ItemsSource = this.viewModel.Settings.IndexSearchExcludedSubdirectoryPaths;
actionKeywordsListView = new List<ActionKeywordView>
{
new ActionKeywordView()
{
Description = viewModel.Context.API.GetTranslation("plugin_explorer_actionkeywordview_search"),
Keyword = this.viewModel.Settings.SearchActionKeyword
},
new ActionKeywordView()
{
Description = viewModel.Context.API.GetTranslation("plugin_explorer_actionkeywordview_filecontentsearch"),
Keyword = this.viewModel.Settings.FileContentSearchActionKeyword
}
};
lbxActionKeywords.ItemsSource = actionKeywordsListView;
RefreshView();
}
@ -43,9 +62,14 @@ namespace Flow.Launcher.Plugin.Explorer.Views
btnEdit.Visibility = Visibility.Hidden;
btnAdd.Visibility = Visibility.Hidden;
if (expFolderLinks.IsExpanded || expExcludedPaths.IsExpanded)
if (expFolderLinks.IsExpanded || expExcludedPaths.IsExpanded || expActionKeywords.IsExpanded)
{
btnAdd.Visibility = Visibility.Visible;
if (!expActionKeywords.IsExpanded)
btnAdd.Visibility = Visibility.Visible;
if (expActionKeywords.IsExpanded
&& btnEdit.Visibility == Visibility.Hidden)
btnEdit.Visibility = Visibility.Visible;
if ((lbxFolderLinks.Items.Count == 0 && lbxExcludedPaths.Items.Count == 0)
&& btnDelete.Visibility == Visibility.Visible
@ -77,22 +101,50 @@ namespace Flow.Launcher.Plugin.Explorer.Views
lbxFolderLinks.Items.Refresh();
lbxExcludedPaths.Items.Refresh();
lbxActionKeywords.Items.Refresh();
}
private void expActionKeywords_Click(object sender, RoutedEventArgs e)
{
if (expActionKeywords.IsExpanded)
expActionKeywords.Height = 215;
if (expExcludedPaths.IsExpanded)
expExcludedPaths.IsExpanded = false;
if (expFolderLinks.IsExpanded)
expFolderLinks.IsExpanded = false;
RefreshView();
}
private void expActionKeywords_Collapsed(object sender, RoutedEventArgs e)
{
if (!expActionKeywords.IsExpanded)
expActionKeywords.Height = Double.NaN;
}
private void expFolderLinks_Click(object sender, RoutedEventArgs e)
{
if (expFolderLinks.IsExpanded)
expFolderLinks.Height = 235;
if (!expFolderLinks.IsExpanded)
expFolderLinks.Height = Double.NaN;
expFolderLinks.Height = 215;
if (expExcludedPaths.IsExpanded)
expExcludedPaths.IsExpanded = false;
if (expActionKeywords.IsExpanded)
expActionKeywords.IsExpanded = false;
RefreshView();
}
private void expFolderLinks_Collapsed(object sender, RoutedEventArgs e)
{
if (!expFolderLinks.IsExpanded)
expFolderLinks.Height = Double.NaN;
}
private void expExcludedPaths_Click(object sender, RoutedEventArgs e)
{
if (expExcludedPaths.IsExpanded)
@ -101,6 +153,9 @@ namespace Flow.Launcher.Plugin.Explorer.Views
if (expFolderLinks.IsExpanded)
expFolderLinks.IsExpanded = false;
if (expActionKeywords.IsExpanded)
expActionKeywords.IsExpanded = false;
RefreshView();
}
@ -132,33 +187,46 @@ namespace Flow.Launcher.Plugin.Explorer.Views
private void btnEdit_Click(object sender, RoutedEventArgs e)
{
var selectedRow = lbxFolderLinks.SelectedItem as FolderLink ?? lbxExcludedPaths.SelectedItem as FolderLink;
if (selectedRow != null)
if (lbxActionKeywords.SelectedItem is ActionKeywordView)
{
var folderBrowserDialog = new FolderBrowserDialog();
folderBrowserDialog.SelectedPath = selectedRow.Path;
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
if (expFolderLinks.IsExpanded)
{
var link = viewModel.Settings.QuickFolderAccessLinks.First(x => x.Path == selectedRow.Path);
link.Path = folderBrowserDialog.SelectedPath;
}
var selectedActionKeyword = lbxActionKeywords.SelectedItem as ActionKeywordView;
if (expExcludedPaths.IsExpanded)
{
var link = viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.First(x => x.Path == selectedRow.Path);
link.Path = folderBrowserDialog.SelectedPath;
}
}
var actionKeywordWindow = new ActionKeywordSetting(viewModel, actionKeywordsListView, selectedActionKeyword);
actionKeywordWindow.ShowDialog();
RefreshView();
}
else
{
string warning = viewModel.Context.API.GetTranslation("plugin_explorer_select_folder_link_warning");
MessageBox.Show(warning);
var selectedRow = lbxFolderLinks.SelectedItem as FolderLink ?? lbxExcludedPaths.SelectedItem as FolderLink;
if (selectedRow != null)
{
var folderBrowserDialog = new FolderBrowserDialog();
folderBrowserDialog.SelectedPath = selectedRow.Path;
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
if (expFolderLinks.IsExpanded)
{
var link = viewModel.Settings.QuickFolderAccessLinks.First(x => x.Path == selectedRow.Path);
link.Path = folderBrowserDialog.SelectedPath;
}
if (expExcludedPaths.IsExpanded)
{
var link = viewModel.Settings.IndexSearchExcludedSubdirectoryPaths.First(x => x.Path == selectedRow.Path);
link.Path = folderBrowserDialog.SelectedPath;
}
}
RefreshView();
}
else
{
string warning = viewModel.Context.API.GetTranslation("plugin_explorer_make_selection_warning");
MessageBox.Show(warning);
}
}
}
@ -242,4 +310,11 @@ namespace Flow.Launcher.Plugin.Explorer.Views
viewModel.OpenWindowsIndexingOptions();
}
}
public class ActionKeywordView
{
public string Description { get; set; }
public string Keyword { get; set; }
}
}

View file

@ -1,10 +1,13 @@
{
"ID": "572be03c74c642baae319fc283e561a8",
"ActionKeyword": "*",
"ActionKeywords": [
"*",
"doc:"
],
"Name": "Explorer",
"Description": "Search and manage files and folders. Explorer utilises Windows Index Search",
"Author": "Jeremy Wu",
"Version": "1.1.0",
"Version": "1.2.4",
"Language": "csharp",
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",

View file

@ -5,7 +5,8 @@
<system:String x:Key="flowlauncher_plugin_processkiller_plugin_name">Process Killer</system:String>
<system:String x:Key="flowlauncher_plugin_processkiller_plugin_description">Kill running processes from Flow Launcher</system:String>
<system:String x:Key="flowlauncher_plugin_processkiller_kill_all">kill all "{0}" processes</system:String>
<system:String x:Key="flowlauncher_plugin_processkiller_kill_all">kill all instances of "{0}"</system:String>
<system:String x:Key="flowlauncher_plugin_processkiller_kill_all_count">kill {0} processes</system:String>
<system:String x:Key="flowlauncher_plugin_processkiller_kill_instances">kill all instances</system:String>
</ResourceDictionary>

View file

@ -52,21 +52,24 @@ namespace Flow.Launcher.Plugin.ProcessKiller
// get all non-system processes whose file path matches that of the given result (processPath)
var similarProcesses = processHelper.GetSimilarProcesses(processPath);
menuOptions.Add(new Result
if (similarProcesses.Count() > 0)
{
Title = _context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_instances"),
SubTitle = processPath,
Action = _ =>
menuOptions.Add(new Result
{
foreach (var p in similarProcesses)
Title = _context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_instances"),
SubTitle = processPath,
Action = _ =>
{
processHelper.TryKill(p);
}
foreach (var p in similarProcesses)
{
processHelper.TryKill(p);
}
return true;
},
IcoPath = processPath
});
return true;
},
IcoPath = processPath
});
}
return menuOptions;
}
@ -86,6 +89,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller
SubTitle = path,
TitleHighlightData = StringMatcher.FuzzySearch(termToSearch, p.ProcessName).MatchData,
Score = pr.Score,
ContextData = p.ProcessName,
Action = (c) =>
{
processHelper.TryKill(p);
@ -94,16 +98,18 @@ namespace Flow.Launcher.Plugin.ProcessKiller
});
}
var sortedResults = results.OrderBy(x => x.Title).ToList();
// When there are multiple results AND all of them are instances of the same executable
// add a quick option to kill them all at the top of the results.
var firstResult = results.FirstOrDefault()?.SubTitle;
if (processlist.Count > 1 && !string.IsNullOrEmpty(termToSearch) && results.All(r => r.SubTitle == firstResult))
var firstResult = sortedResults.FirstOrDefault(x => !string.IsNullOrEmpty(x.SubTitle));
if (processlist.Count > 1 && !string.IsNullOrEmpty(termToSearch) && sortedResults.All(r => r.SubTitle == firstResult?.SubTitle))
{
results.Insert(0, new Result()
sortedResults.Insert(1, new Result()
{
IcoPath = "Images/app.png",
Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), termToSearch),
SubTitle = "",
IcoPath = firstResult?.IcoPath,
Title = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all"), firstResult?.ContextData),
SubTitle = string.Format(_context.API.GetTranslation("flowlauncher_plugin_processkiller_kill_all_count"), processlist.Count),
Score = 200,
Action = (c) =>
{
@ -117,7 +123,7 @@ namespace Flow.Launcher.Plugin.ProcessKiller
});
}
return results;
return sortedResults;
}
}
}

View file

@ -4,7 +4,7 @@
"Name":"Process Killer",
"Description":"kill running processes from Flow",
"Author":"Flow-Launcher",
"Version":"1.0.0",
"Version":"1.1.0",
"Language":"csharp",
"Website":"https://github.com/Flow-Launcher/Flow.Launcher.Plugin.ProcessKiller",
"IcoPath":"Images\\app.png",

View file

@ -40,7 +40,12 @@
<system:String x:Key="flowlauncher_plugin_program_plugin_description">Search programs in Flow Launcher</system:String>
<system:String x:Key="flowlauncher_plugin_program_invalid_path">Invalid Path</system:String>
<system:String x:Key="flowlauncher_plugin_program_customizedexplorer">Customized Explorer</system:String>
<system:String x:Key="flowlauncher_plugin_program_args">Args</system:String>
<system:String x:Key="flowlauncher_plugin_program_tooltip_customizedexplorer">You can customized the explorer used for opening the container folder by inputing the Environmental Variable of the explorer you want to use. It will be useful to use CMD to test whether the Environmental Variable is avaliable.</system:String>
<system:String x:Key="flowlauncher_plugin_program_tooltip_args">Enter the customized args you want to add for your customized explorer. %s for parent directory, %f for full path (which only works for win32). Check the explorer's website for details.</system:String>
<!--Dialogs-->
<system:String x:Key="flowlauncher_plugin_program_disable_dlgtitle_success">Success</system:String>
<system:String x:Key="flowlauncher_plugin_program_disable_dlgtitle_success_message">Successfully disabled this program from displaying in your query</system:String>

View file

@ -324,7 +324,9 @@ namespace Flow.Launcher.Plugin.Program.Programs
Action = _ =>
{
Main.StartProcess(Process.Start, new ProcessStartInfo(Package.Location));
Main.StartProcess(Process.Start, new ProcessStartInfo(
!string.IsNullOrEmpty(Main._settings.CustomizedExplorer) ? Main._settings.CustomizedExplorer:Settings.Explorer,
Main._settings.CustomizedArgs.Replace("%s",$"\"{Package.Location}\"").Trim()));
return true;
},
@ -536,7 +538,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
ProgramLogger.LogException($"|UWP|ImageFromPath|{path}" +
$"|Unable to get logo for {UserModelId} from {path} and" +
$" located in {Package.Location}", new FileNotFoundException());
return new BitmapImage(new Uri(Constant.ErrorIcon));
return new BitmapImage(new Uri(Constant.MissingImgIcon));
}
}
@ -586,7 +588,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
$"|Unable to convert background string {BackgroundColor} " +
$"to color for {Package.Location}", new InvalidOperationException());
return new BitmapImage(new Uri(Constant.ErrorIcon));
return new BitmapImage(new Uri(Constant.MissingImgIcon));
}
}
else

View file

@ -140,10 +140,19 @@ namespace Flow.Launcher.Plugin.Program.Programs
Title = api.GetTranslation("flowlauncher_plugin_program_open_containing_folder"),
Action = _ =>
{
var args = !string.IsNullOrWhiteSpace(Main._settings.CustomizedArgs)
?Main._settings.CustomizedArgs
.Replace("%s",$"\"{ParentDirectory}\"")
.Replace("%f",$"\"{FullPath}\""):
Main._settings.CustomizedExplorer==Settings.Explorer
? $"/select,\"{FullPath}\""
: Settings.ExplorerArgs;
Main.StartProcess(Process.Start, new ProcessStartInfo("explorer", ParentDirectory));
Main.StartProcess(Process.Start, new ProcessStartInfo(
!string.IsNullOrWhiteSpace(Main._settings.CustomizedExplorer)
? Main._settings.CustomizedExplorer
: Settings.Explorer,
args));
return true;
},
IcoPath = "Images/folder.png"

View file

@ -14,9 +14,15 @@ namespace Flow.Launcher.Plugin.Program
public bool EnableStartMenuSource { get; set; } = true;
public bool EnableRegistrySource { get; set; } = true;
public string CustomizedExplorer { get; set; } = Explorer;
public string CustomizedArgs { get; set; } = ExplorerArgs;
internal const char SuffixSeperator = ';';
internal const string Explorer = "explorer";
internal const string ExplorerArgs = "%s";
/// <summary>
/// Contains user added folder location contents as well as all user disabled applications
/// </summary>

View file

@ -5,12 +5,13 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:program="clr-namespace:Flow.Launcher.Plugin.Program"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="600">
d:DesignHeight="404.508" d:DesignWidth="600">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="75"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Stretch">
<StackPanel Orientation="Vertical" Width="205">
@ -24,7 +25,7 @@
</StackPanel>
</StackPanel>
<ListView x:Name="programSourceView" Grid.Row="1" AllowDrop="True" SelectionMode="Extended" Style="{StaticResource {x:Static GridView.GridViewStyleKey}}"
Margin="0 13 0 0"
Margin="0,13,0,10"
BorderBrush="DarkGray"
BorderThickness="1"
GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler"
@ -63,7 +64,7 @@
</GridView>
</ListView.View>
</ListView>
<DockPanel Grid.Row="2">
<DockPanel Grid.Row="2" Margin="0,0,0,0" Grid.RowSpan="1">
<StackPanel x:Name="indexingPanel" Visibility="Hidden" HorizontalAlignment="Left" Orientation="Horizontal">
<ProgressBar x:Name="progressBarIndexing" Height="20" Width="80" Minimum="0" Maximum="100" IsIndeterminate="True" />
<TextBlock Margin="10 0 0 0" Height="20" HorizontalAlignment="Center" Text="{DynamicResource flowlauncher_plugin_program_indexing}" />
@ -71,9 +72,18 @@
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button x:Name="btnProgramSourceStatus" Click="btnProgramSourceStatus_OnClick" Width="100" Margin="10" Content="{DynamicResource flowlauncher_plugin_program_disable}" />
<Button x:Name="btnEditProgramSource" Click="btnEditProgramSource_OnClick" Width="100" Margin="10" Content="{DynamicResource flowlauncher_plugin_program_edit}"/>
<Button x:Name="btnAddProgramSource" Click="btnAddProgramSource_OnClick" Width="100" Margin="10" Content="{DynamicResource flowlauncher_plugin_program_add}"/>
<Button x:Name="btnAddProgramSource" Click="btnAddProgramSource_OnClick" Width="100" Margin="10 10 0 10" Content="{DynamicResource flowlauncher_plugin_program_add}"/>
</StackPanel>
</DockPanel>
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Stretch">
<TextBlock Text="{DynamicResource flowlauncher_plugin_program_customizedexplorer}" VerticalAlignment="Center" FontSize="15"
ToolTip= "{DynamicResource flowlauncher_plugin_program_tooltip_customizedexplorer}"/>
<TextBox Margin="10,0,10,0" TextWrapping="NoWrap" VerticalAlignment="Center" Width="200" Height="30" FontSize="15"
TextChanged="CustomizeExplorer" x:Name="CustomizeExplorerBox"/>
<TextBlock Text="{DynamicResource flowlauncher_plugin_program_args}" VerticalAlignment="Center" FontSize="15"
ToolTip="{DynamicResource flowlauncher_plugin_program_tooltip_args}" />
<TextBox Margin="10,0,0,0" Width="135" Height="30" FontSize="15" x:Name="CustomizeArgsBox" TextChanged="CustomizeExplorerArgs"></TextBox>
</StackPanel>
</Grid>
</UserControl>

View file

@ -43,6 +43,9 @@ namespace Flow.Launcher.Plugin.Program.Views
StartMenuEnabled.IsChecked = _settings.EnableStartMenuSource;
RegistryEnabled.IsChecked = _settings.EnableRegistrySource;
CustomizeExplorerBox.Text = _settings.CustomizedExplorer;
CustomizeArgsBox.Text = _settings.CustomizedArgs;
ViewRefresh();
}
@ -326,5 +329,15 @@ namespace Flow.Launcher.Plugin.Program.Views
btnProgramSourceStatus.Content = "Enable";
}
}
private void CustomizeExplorer(object sender, TextChangedEventArgs e)
{
_settings.CustomizedExplorer = CustomizeExplorerBox.Text;
}
private void CustomizeExplorerArgs(object sender, TextChangedEventArgs e)
{
_settings.CustomizedArgs = CustomizeArgsBox.Text;
}
}
}

View file

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

View file

@ -25,6 +25,7 @@
<system:String x:Key="flowlauncher_plugin_websearch_input_url">Please enter a URL</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_action_keyword_exist">Action keyword already exists, please enter a different one</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_succeed">Success</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_iconpath_hint">Hint: You do not need to place custom images in this directory, if Flow's version is updated they will be lost. Flow will automatically copy any images outside of this directory across to WebSearch's custom image location.</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_plugin_name">Web Searches</system:String>
<system:String x:Key="flowlauncher_plugin_websearch_plugin_description">Allows to perform web searches</system:String>

View file

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using System.Windows.Controls;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Plugin.WebSearch
@ -21,8 +22,9 @@ namespace Flow.Launcher.Plugin.WebSearch
private CancellationTokenSource _updateSource;
private CancellationToken _updateToken;
public const string Images = "Images";
public static string ImagesDirectory;
internal const string Images = "Images";
internal static string DefaultImagesDirectory;
internal static string CustomImagesDirectory;
private readonly string SearchSourceGlobalPluginWildCardSign = "*";
@ -172,8 +174,14 @@ namespace Flow.Launcher.Plugin.WebSearch
_context = context;
var pluginDirectory = _context.CurrentPluginMetadata.PluginDirectory;
var bundledImagesDirectory = Path.Combine(pluginDirectory, Images);
ImagesDirectory = Path.Combine(_context.CurrentPluginMetadata.PluginDirectory, Images);
Helper.ValidateDataDirectory(bundledImagesDirectory, ImagesDirectory);
// Default images directory is in the WebSearch's application folder
DefaultImagesDirectory = Path.Combine(pluginDirectory, Images);
Helper.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory);
// Custom images directory is in the WebSearch's data location folder
var name = Path.GetFileNameWithoutExtension(_context.CurrentPluginMetadata.ExecuteFileName);
CustomImagesDirectory = Path.Combine(DataLocation.PluginSettingsDirectory, name, "CustomIcons");
}
#region ISettingProvider Members

View file

@ -1,29 +1,38 @@
using System.IO;
using System.IO;
using System.Windows.Media;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure;
using System.Reflection;
namespace Flow.Launcher.Plugin.WebSearch
{
public class SearchSource : BaseModel
{
public const string DefaultIcon = "web_search.png";
public string Title { get; set; }
public string ActionKeyword { get; set; }
[NotNull]
public string Icon { get; set; } = DefaultIcon;
public string Icon { get; set; } = "web_search.png";
public bool CustomIcon { get; set; } = false;
/// <summary>
/// All icon should be put under Images directory
/// Default icons are placed in Images directory in the app location.
/// Custom icons are placed in the user data directory
/// </summary>
[NotNull]
[JsonIgnore]
internal string IconPath => Path.Combine(Main.ImagesDirectory, Icon);
public string IconPath
{
get
{
if (CustomIcon)
return Path.Combine(Main.CustomImagesDirectory, Icon);
[JsonIgnore]
public ImageSource Image => ImageLoader.Load(IconPath);
return Path.Combine(Main.DefaultImagesDirectory, Icon);
}
}
public string Url { get; set; }
public bool Enabled { get; set; }
@ -36,6 +45,7 @@ namespace Flow.Launcher.Plugin.WebSearch
ActionKeyword = string.Copy(ActionKeyword),
Url = string.Copy(Url),
Icon = string.Copy(Icon),
CustomIcon = CustomIcon,
Enabled = Enabled
};
return webSearch;

View file

@ -47,7 +47,7 @@
<TextBlock Margin="10" FontSize="14" Grid.Row="4" Grid.Column="0" VerticalAlignment="Center"
HorizontalAlignment="Right" Text="{DynamicResource flowlauncher_plugin_websearch_icon}" />
<StackPanel Orientation="Horizontal" Grid.Row="4" Grid.Column="1" Margin="10">
<Image Source="{Binding SearchSource.Image ,IsAsync=True}" Width="24" Height="24" Margin="0 0 20 0" />
<Image Name="imgPreviewIcon" Width="24" Height="24" Margin="0 0 20 0" />
<Button Click="OnSelectIconClick" Height="35"
Content="{DynamicResource flowlauncher_plugin_websearch_select_icon}" />
</StackPanel>

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.IO;
using System.Windows;
using Microsoft.Win32;
using Flow.Launcher.Core.Plugin;
@ -15,6 +14,7 @@ namespace Flow.Launcher.Plugin.WebSearch
private PluginInitContext _context;
private IPublicAPI _api;
private SearchSourceViewModel _viewModel;
private string selectedNewIconImageFullPath;
public SearchSourceSettingWindow(IList<SearchSource> sources, PluginInitContext context, SearchSource old)
@ -39,6 +39,10 @@ namespace Flow.Launcher.Plugin.WebSearch
_action = action;
_context = context;
_api = _context.API;
_viewModel.SetupCustomImagesDirectory();
imgPreviewIcon.Source = _viewModel.LoadPreviewIcon(_searchSource.IconPath);
}
private void OnCancelButtonClick(object sender, RoutedEventArgs e)
@ -111,26 +115,32 @@ namespace Flow.Launcher.Plugin.WebSearch
var warning = _api.GetTranslation("newActionKeywordsHasBeenAssigned");
MessageBox.Show(warning);
}
if (!string.IsNullOrEmpty(selectedNewIconImageFullPath))
{
_viewModel.UpdateIconAttributes(_searchSource, selectedNewIconImageFullPath);
_viewModel.CopyNewImageToUserDataDirectoryIfRequired(
_searchSource, selectedNewIconImageFullPath, _oldSearchSource.IconPath);
}
}
private void OnSelectIconClick(object sender, RoutedEventArgs e)
{
var directory = Main.ImagesDirectory;
const string filter = "Image files (*.jpg, *.jpeg, *.gif, *.png, *.bmp) |*.jpg; *.jpeg; *.gif; *.png; *.bmp";
var dialog = new OpenFileDialog {InitialDirectory = directory, Filter = filter};
var dialog = new OpenFileDialog {InitialDirectory = Main.CustomImagesDirectory, Filter = filter};
var result = dialog.ShowDialog();
if (result == true)
{
var fullpath = dialog.FileName;
if (!string.IsNullOrEmpty(fullpath))
selectedNewIconImageFullPath = dialog.FileName;
if (!string.IsNullOrEmpty(selectedNewIconImageFullPath))
{
_searchSource.Icon = Path.GetFileName(fullpath);
if (!File.Exists(_searchSource.IconPath))
{
_searchSource.Icon = SearchSource.DefaultIcon;
MessageBox.Show($"The file should be put under {directory}");
}
if (_viewModel.ShouldProvideHint(selectedNewIconImageFullPath))
MessageBox.Show(_api.GetTranslation("flowlauncher_plugin_websearch_iconpath_hint"));
imgPreviewIcon.Source = _viewModel.LoadPreviewIcon(selectedNewIconImageFullPath);
}
}
}

View file

@ -1,7 +1,65 @@
namespace Flow.Launcher.Plugin.WebSearch
using Flow.Launcher.Infrastructure.Image;
using System;
using System.Drawing;
using System.IO;
using System.Windows;
using System.Windows.Media;
namespace Flow.Launcher.Plugin.WebSearch
{
public class SearchSourceViewModel
public class SearchSourceViewModel : BaseModel
{
public SearchSource SearchSource { get; set; }
public void UpdateIconAttributes(SearchSource selectedSearchSource, string fullpathToSelectedImage)
{
var parentDirectorySelectedImg = Directory.GetParent(fullpathToSelectedImage).ToString();
selectedSearchSource.CustomIcon = parentDirectorySelectedImg != Main.DefaultImagesDirectory;
var iconFileName = Path.GetFileName(fullpathToSelectedImage);
selectedSearchSource.Icon = iconFileName;
}
public void CopyNewImageToUserDataDirectoryIfRequired(
SearchSource selectedSearchSource, string fullpathToSelectedImage, string fullPathToOriginalImage)
{
var destinationFileNameFullPath = Path.Combine(Main.CustomImagesDirectory, Path.GetFileName(fullpathToSelectedImage));
var parentDirectorySelectedImg = Directory.GetParent(fullpathToSelectedImage).ToString();
if (parentDirectorySelectedImg != Main.CustomImagesDirectory && parentDirectorySelectedImg != Main.DefaultImagesDirectory)
{
try
{
File.Copy(fullpathToSelectedImage, destinationFileNameFullPath);
}
catch (Exception e)
{
#if DEBUG
throw e;
#else
MessageBox.Show(string.Format("Copying the selected image file to {0} has failed, changes will now be reverted", destinationFileNameFullPath));
UpdateIconAttributes(selectedSearchSource, fullPathToOriginalImage);
#endif
}
}
}
internal void SetupCustomImagesDirectory()
{
if (!Directory.Exists(Main.CustomImagesDirectory))
Directory.CreateDirectory(Main.CustomImagesDirectory);
}
internal bool ShouldProvideHint(string fullPathToSelectedImage)
{
return Directory.GetParent(fullPathToSelectedImage).ToString() == Main.DefaultImagesDirectory;
}
internal ImageSource LoadPreviewIcon(string pathToPreviewIconImage)
{
return ImageLoader.Load(pathToPreviewIconImage);
}
}
}

View file

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

View file

@ -7,6 +7,7 @@
[![Github All Releases](https://img.shields.io/github/downloads/Flow-Launcher/Flow.Launcher/total.svg)](https://github.com/Flow-Launcher/Flow.Launcher/releases)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/Flow-Launcher/Flow.Launcher)](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest)
![GitHub Release Date](https://img.shields.io/github/release-date/Flow-Launcher/Flow.Launcher)
[![Discord](https://img.shields.io/discord/727828229250875472?color=7389D8&labelColor=6A7EC2&label=Community&logo=discord&logoColor=white)](https://discord.gg/AvgAQgh)
Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at being more than an app launcher, it searches, integrates and expands on functionalities. Flow will continue to evolve, designed to be open and built with the community at heart.
@ -16,6 +17,7 @@ Flow Launcher. Dedicated to make your workflow flow more seamlessly. Aimed at be
![The Flow](https://user-images.githubusercontent.com/26427004/82151677-fa9c7100-989f-11ea-9143-81de60aaf07d.gif)
- Search everything from applications, files, bookmarks, YouTube, Twitter and more. All from the comfort of your keyboard without ever touching the mouse.
- Search for file contents
- Support search using environment variable paths
- Run batch and PowerShell commands as Administrator or a different user.
- Support languages from Chinese to Italian and more.
@ -58,6 +60,8 @@ Get in touch if you like to join the Flow-Launcher Team and help build this grea
Yes please, submit an issue to let us know.
**Join our community on [Discord](https://discord.gg/AvgAQgh)!**
## Developing/Debugging
Flow Launcher's target framework is .Net Core 3.1

View file

@ -16,6 +16,6 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: AssemblyVersion("1.1.2")]
[assembly: AssemblyFileVersion("1.1.2")]
[assembly: AssemblyInformationalVersion("1.1.2")]
[assembly: AssemblyVersion("1.3.1")]
[assembly: AssemblyFileVersion("1.3.1")]
[assembly: AssemblyInformationalVersion("1.3.1")]

View file

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