diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fe23fc..274907e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,52 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -- Add quotes escaping +## [1.1.8] - 2024-09-xx + +- Add autocomplete support for commands with arguments +- After typing the full shortcut, for example `q dev`, pressing `Ctr+Tab` (autocomplete) will replace current query with + the + shortcut path +- Alias support for shortcuts. Add `Alias` field in the shortcut JSON file. Provide the alias name. Example: + + ```json + { + "Type": "Directory", + "Path": "C:\\Users\\tutta\\Storage\\Dev", + "Key": "dev", + "Alias": ["development", "devs"] + } + ``` +- Show associated icons for shortcuts in the search results +- New plugin icon +- Customizable icons for shortcuts. In order to use custom icons, add `Icon` field in the shortcut JSON file. + Provide the full path to the icon file. Example: + + ```json + { + "Type": "Directory", + "Path": "C:\\Users\\tutta\\Storage\\Dev", + "Key": "dev", + "Icon": "C:\\Users\\tutta\\Storage\\Dev\\Projects\\ShortcutPlugin\\Flow.Launcher.Plugin.ShortcutPlugin\\Images\\discord-mark-white.png" + } + ``` + +## [1.1.7] - 2024-06-21 + +- New backup command with subcommands. +- Added copy result title and subtitle options in the context menu +- Fix issue with cmd arguments quotes +- Support for VS Code and VS Code - Insiders in context menu for folders +- Help command with documentation, issues links and developer Discord username + +## [1.1.6] - 2024-05-08 + +- Improved possible shortcuts fuzzy search. Now fuzzy search is case insensitive. +- Version command to see the current plugin version. +- Fix the disappearing config file after updating the plugin using default shortcuts and variables paths. This should + start working updating from **v1.1.6** to future releases. +- Changed default shortcuts and variable paths. +- Reset button in the settings panel to set default settings values. ## [1.1.5] - 2024-04-05 diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/ContextMenu.cs b/Flow.Launcher.Plugin.ShortcutPlugin/ContextMenu.cs index f916e41..a9f04d7 100644 --- a/Flow.Launcher.Plugin.ShortcutPlugin/ContextMenu.cs +++ b/Flow.Launcher.Plugin.ShortcutPlugin/ContextMenu.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.IO; -using System.Windows; using Flow.Launcher.Plugin.ShortcutPlugin.Extensions; using Flow.Launcher.Plugin.ShortcutPlugin.Models.Shortcuts; using Flow.Launcher.Plugin.ShortcutPlugin.Services.Interfaces; @@ -15,16 +13,19 @@ namespace Flow.Launcher.Plugin.ShortcutPlugin; internal class ContextMenu : IContextMenu { private readonly IVariablesService _variablesService; + private readonly PluginInitContext _context; - public ContextMenu(IVariablesService variablesService) + public ContextMenu(IVariablesService variablesService, PluginInitContext context) { _variablesService = variablesService; + _context = context; } public List LoadContextMenus(Result selectedResult) { var contextMenu = new List(); + AddShortcutDetails(selectedResult, contextMenu); AddCopyTitleAndSubtitle(selectedResult, contextMenu); if (selectedResult.ContextData is not Shortcut shortcut) @@ -45,18 +46,53 @@ public List LoadContextMenus(Result selectedResult) return contextMenu; } - private static void AddCopyTitleAndSubtitle(Result selectedResult, List contextMenu) + private void AddShortcutDetails(Result selectedResult, List contextMenu) + { + if (selectedResult.ContextData is not Shortcut shortcut) + { + return; + } + + contextMenu.Add(ResultExtensions.Result( + "Key", + shortcut.Key, + () => { _context.API.CopyToClipboard(shortcut.Key, showDefaultNotification: false); } + )); + + if (shortcut.Alias is {Count: > 0}) + { + contextMenu.Add(ResultExtensions.Result( + "Alias", + string.Join(", ", shortcut.Alias), + () => + { + _context.API.CopyToClipboard(string.Join(", ", shortcut.Alias), showDefaultNotification: false); + } + )); + } + + if (!string.IsNullOrEmpty(shortcut.Description)) + { + contextMenu.Add(ResultExtensions.Result( + "Description", + shortcut.Description, + () => { _context.API.CopyToClipboard(shortcut.Description, showDefaultNotification: false); } + )); + } + } + + private void AddCopyTitleAndSubtitle(Result selectedResult, List contextMenu) { var copyTitle = ResultExtensions.Result( "Copy result title", selectedResult.Title, - action: () => { Clipboard.SetText(selectedResult.Title); }, + action: () => { _context.API.CopyToClipboard(selectedResult.Title, showDefaultNotification: false); }, iconPath: Icons.Copy ); var copySubTitle = ResultExtensions.Result( "Copy result subtitle", selectedResult.SubTitle, - action: () => { Clipboard.SetText(selectedResult.SubTitle); }, + action: () => { _context.API.CopyToClipboard(selectedResult.SubTitle, showDefaultNotification: false); }, iconPath: Icons.Copy ); @@ -106,13 +142,13 @@ private void GetFileShortcutContextMenu(ICollection contextMenu, FileSho contextMenu.Add(ResultExtensions.Result( "Copy path", - action: () => { Clipboard.SetText(filePath); }, + action: () => { _context.API.CopyToClipboard(filePath, showDefaultNotification: false); }, iconPath: Icons.Copy )); contextMenu.Add(ResultExtensions.Result( "Copy file", - action: () => { Clipboard.SetFileDropList(new StringCollection {filePath}); }, + action: () => { _context.API.CopyToClipboard(filePath, true, false); }, iconPath: Icons.Copy )); } @@ -180,12 +216,12 @@ private void GetDirectoryContextMenu(ICollection contextMenu, DirectoryS }; Process.Start(processStartInfo); }, - iconPath: Icons.PowerShell + iconPath: Icons.PowerShellBlack )); contextMenu.Add(ResultExtensions.Result( "Copy path", - action: () => { Clipboard.SetText(directoryPath); }, + action: () => { _context.API.CopyToClipboard(directoryPath, showDefaultNotification: false); }, iconPath: Icons.Copy )); diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Extensions/ResultExtensions.cs b/Flow.Launcher.Plugin.ShortcutPlugin/Extensions/ResultExtensions.cs index 90b35ee..e28f114 100644 --- a/Flow.Launcher.Plugin.ShortcutPlugin/Extensions/ResultExtensions.cs +++ b/Flow.Launcher.Plugin.ShortcutPlugin/Extensions/ResultExtensions.cs @@ -16,16 +16,6 @@ public static List EmptyResult(string title, string subtitle = "") return SingleResult(title, subtitle); } - /*public static List InitializedResult() - { - return SingleResult(Resources.ShortcutsManager_Init_Plugin_initialized); - }*/ - - /*public static List NotImplementedResult() - { - return SingleResult("Not implemented yet", "Please wait for the next release"); - }*/ - public static List SingleResult( string title, string subtitle = "", diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Flow.Launcher.Plugin.ShortcutPlugin.csproj b/Flow.Launcher.Plugin.ShortcutPlugin/Flow.Launcher.Plugin.ShortcutPlugin.csproj index 10f5aa0..d5bc2b8 100644 --- a/Flow.Launcher.Plugin.ShortcutPlugin/Flow.Launcher.Plugin.ShortcutPlugin.csproj +++ b/Flow.Launcher.Plugin.ShortcutPlugin/Flow.Launcher.Plugin.ShortcutPlugin.csproj @@ -35,7 +35,7 @@ - + diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Images/icon.png b/Flow.Launcher.Plugin.ShortcutPlugin/Images/icon.png index 589a1b1..c824230 100644 Binary files a/Flow.Launcher.Plugin.ShortcutPlugin/Images/icon.png and b/Flow.Launcher.Plugin.ShortcutPlugin/Images/icon.png differ diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Images/powershell.png b/Flow.Launcher.Plugin.ShortcutPlugin/Images/powershell.png deleted file mode 100644 index 725a736..0000000 Binary files a/Flow.Launcher.Plugin.ShortcutPlugin/Images/powershell.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Images/powershell_black.png b/Flow.Launcher.Plugin.ShortcutPlugin/Images/powershell_black.png new file mode 100644 index 0000000..bba1843 Binary files /dev/null and b/Flow.Launcher.Plugin.ShortcutPlugin/Images/powershell_black.png differ diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Models/Shortcuts/PluginShortcut.cs b/Flow.Launcher.Plugin.ShortcutPlugin/Models/Shortcuts/PluginShortcut.cs deleted file mode 100644 index 05f0b8a..0000000 --- a/Flow.Launcher.Plugin.ShortcutPlugin/Models/Shortcuts/PluginShortcut.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Flow.Launcher.Plugin.ShortcutPlugin.Models.Shortcuts; - -public class PluginShortcut : Shortcut -{ - public string PluginName { get; init; } - - public string RawQuery { get; init; } - - public override object Clone() - { - return new PluginShortcut - { - Key = Key, - PluginName = PluginName, - RawQuery = RawQuery - }; - } - - public override string ToString() - { - return $"{PluginName} {RawQuery}"; - } -} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Models/Shortcuts/Shortcut.cs b/Flow.Launcher.Plugin.ShortcutPlugin/Models/Shortcuts/Shortcut.cs index ce8e728..9ee8dc9 100644 --- a/Flow.Launcher.Plugin.ShortcutPlugin/Models/Shortcuts/Shortcut.cs +++ b/Flow.Launcher.Plugin.ShortcutPlugin/Models/Shortcuts/Shortcut.cs @@ -1,6 +1,6 @@ using System; +using System.Collections.Generic; using System.Text.Json.Serialization; -using Flow.Launcher.Plugin.ShortcutPlugin.Utilities; namespace Flow.Launcher.Plugin.ShortcutPlugin.Models.Shortcuts; @@ -10,11 +10,16 @@ namespace Flow.Launcher.Plugin.ShortcutPlugin.Models.Shortcuts; [JsonDerivedType(typeof(FileShortcut), nameof(ShortcutType.File))] [JsonDerivedType(typeof(ShellShortcut), nameof(ShortcutType.Shell))] [JsonDerivedType(typeof(GroupShortcut), nameof(ShortcutType.Group))] -[JsonDerivedType(typeof(PluginShortcut), nameof(ShortcutType.Plugin))] public abstract class Shortcut : ICloneable { public string Key { get; set; } + public List Alias { get; set; } + + public string Description { get; set; } + + public string Icon { get; set; } + public string GetDerivedType() { return this switch @@ -22,37 +27,22 @@ public string GetDerivedType() FileShortcut => "File", DirectoryShortcut => "Directory", UrlShortcut => "Url", - PluginShortcut => "Plugin", GroupShortcut => "Group", ShellShortcut => "Shell", _ => "Unspecified shortcut type" }; } - public string GetIcon() + public string GetTitle() { - return this switch - { - FileShortcut => Icons.File, - DirectoryShortcut => Icons.Folder, - UrlShortcut => Icons.Link, - ShellShortcut => Icons.Terminal, - PluginShortcut => Icons.Logo, - GroupShortcut => Icons.TabGroup, - _ => Icons.Logo - }; + return $"{Key}{GetAlias()}"; } - public ShortcutType GetShortcutType() => this switch + private string GetAlias() { - FileShortcut => ShortcutType.File, - DirectoryShortcut => ShortcutType.Directory, - UrlShortcut => ShortcutType.Url, - ShellShortcut => ShortcutType.Shell, - PluginShortcut => ShortcutType.Plugin, - GroupShortcut => ShortcutType.Group, - _ => ShortcutType.Unspecified - }; + // Alternative symbols: ⨯ ⇒ ⪢ ⌗ + return Alias is {Count: > 0} ? $" ⌗ {string.Join(" ⌗ ", Alias)}" : string.Empty; + } public abstract object Clone(); diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Repositories/CommandsRepository.cs b/Flow.Launcher.Plugin.ShortcutPlugin/Repositories/CommandsRepository.cs index 4c2d4b5..dc2741b 100644 --- a/Flow.Launcher.Plugin.ShortcutPlugin/Repositories/CommandsRepository.cs +++ b/Flow.Launcher.Plugin.ShortcutPlugin/Repositories/CommandsRepository.cs @@ -45,29 +45,29 @@ public List ResolveCommand(List arguments, Query query) return ShowAvailableCommands(query.ActionKeyword); } + var key = arguments[0]; + var argsWithoutKey = arguments.Skip(1).ToList(); + // In case this is a shortcut command, let's open shortcut - if (_shortcutsRepository.GetShortcuts(arguments[0]) is not null) + if (_shortcutsRepository.GetShortcuts(key) is not null) { - return _shortcutsService.OpenShortcuts(arguments[0], arguments.Skip(1).ToList()); // Skips shortcut name + return _shortcutsService.OpenShortcuts(key, argsWithoutKey); } // If command was not found - if (!_commands.TryGetValue(arguments[0], out var command)) + if (!_commands.TryGetValue(key, out var command)) { // Show possible shortcuts var possibleShortcuts = _shortcutsRepository - .GetPossibleShortcuts(arguments[0]) - .Select(s => - _shortcutsService.OpenShortcut(s, arguments.Skip(1).ToList()) - .First() - ); + .GetPossibleShortcuts(key) + .SelectMany(s => _shortcutsService.OpenShortcut(s, argsWithoutKey)); // Return possible command matches - var possibleCommands = GetPossibleCommands(arguments[0], query.ActionKeyword); + var possibleCommands = GetPossibleCommands(key, query.ActionKeyword); var possibleResults = possibleShortcuts.Concat(possibleCommands).ToList(); - return possibleResults.Count != 0 + return possibleResults.Any() ? possibleResults : ResultExtensions.SingleResult( "Invalid input", @@ -95,13 +95,13 @@ public List ResolveCommand(List arguments, Query query) } // If command has more arguments - if (executor.Arguments.Count != 0) + if (executor.Arguments.Any()) { // Can't check because Flow Launcher trims the query - /* if (!query.EndsWith(" ")) + /*if (!query.RawQuery.EndsWith(" ")) { return ResultExtensions.SingleResult(executor.ResponseInfo.Item1, executor.ResponseInfo.Item2); - } */ + }*/ return executor .Arguments.Cast() @@ -109,7 +109,8 @@ public List ResolveCommand(List arguments, Query query) ResultExtensions.Result( a.ResponseInfo.Item1, a.ResponseInfo.Item2, - () => { _context.API.ChangeQuery($"{a.Key} "); } + () => { _context.API.ChangeQuery($"{query.ActionKeyword} {a.ResponseInfo.Item1}"); }, + hideAfterAction: false ) ) .ToList(); diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Repositories/ShortcutsRepository.cs b/Flow.Launcher.Plugin.ShortcutPlugin/Repositories/ShortcutsRepository.cs index 8555b74..7e596b2 100644 --- a/Flow.Launcher.Plugin.ShortcutPlugin/Repositories/ShortcutsRepository.cs +++ b/Flow.Launcher.Plugin.ShortcutPlugin/Repositories/ShortcutsRepository.cs @@ -35,6 +35,7 @@ public IList GetShortcuts() { return _shortcuts.Values .SelectMany(x => x) + .Distinct() .ToList(); } @@ -42,53 +43,53 @@ public IEnumerable GetPossibleShortcuts(string key) { var lowerKey = key.ToLowerInvariant(); - var result = GetShortcuts() - .Select(s => new - { - Shortcut = s, - PartialScore = Fuzz.PartialRatio(s.Key.ToLowerInvariant(), lowerKey), - Score = Fuzz.Ratio(s.Key.ToLowerInvariant(), lowerKey) - }) - .Where(x => x.PartialScore > 90) - .OrderByDescending(x => x.Score) - .Select(x => x.Shortcut) - .ToList(); - - return result; + var shortcuts = GetShortcuts() + .SelectMany(s => (s.Alias ?? Enumerable.Empty()).Append(s.Key), + (s, k) => new {Shortcut = s, Key = k.ToLowerInvariant()}); + + return shortcuts + .Where(x => Fuzz.PartialRatio(x.Key, lowerKey) > 90) + .OrderByDescending(x => Fuzz.Ratio(x.Key, lowerKey)) + .Select(x => x.Shortcut) + .Distinct() + .ToList(); } public void AddShortcut(Shortcut shortcut) { - var result = _shortcuts.TryGetValue(shortcut.Key, out var value); + var keys = (shortcut.Alias ?? Enumerable.Empty()).Append(shortcut.Key); - if (!result) + foreach (var key in keys) { - value = new List(); - _shortcuts.Add(shortcut.Key, value); - } + if (!_shortcuts.TryGetValue(key, out var value)) + { + value = new List(); + _shortcuts.Add(key, value); + } - value.Add(shortcut); + value.Add(shortcut); + } SaveShortcuts(); } public void RemoveShortcut(Shortcut shortcut) { - if (!_shortcuts.TryGetValue(shortcut.Key, out var value)) - { - return; - } - - var result = value.Remove(shortcut); + var keys = (shortcut.Alias ?? Enumerable.Empty()).Append(shortcut.Key); - if (!result) + foreach (var key in keys) { - return; - } + if (!_shortcuts.TryGetValue(key, out var value)) + { + continue; + } - if (value.Count == 0) - { - _shortcuts.Remove(shortcut.Key); + var result = value.Remove(shortcut); + + if (result && value.Count == 0) + { + _shortcuts.Remove(key); + } } SaveShortcuts(); @@ -190,8 +191,11 @@ private Dictionary> ReadShortcuts(string path) var json = File.ReadAllText(path); var shortcuts = JsonSerializer.Deserialize>(json); - return shortcuts.GroupBy(x => x.Key) - .ToDictionary(x => x.Key, x => x.ToList()); + return shortcuts + .SelectMany(s => (s.Alias ?? Enumerable.Empty()).Append(s.Key), + (s, k) => new {Shortcut = s, Key = k}) + .GroupBy(x => x.Key) + .ToDictionary(x => x.Key, x => x.Select(y => y.Shortcut).ToList()); } catch (Exception e) { @@ -212,6 +216,7 @@ private void SaveShortcuts() var flattenShortcuts = _shortcuts.Values .SelectMany(x => x) + .Distinct() .ToList(); var json = JsonSerializer.Serialize(flattenShortcuts, options); diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Services/CommandsService.cs b/Flow.Launcher.Plugin.ShortcutPlugin/Services/CommandsService.cs index f613f11..a5d76b7 100644 --- a/Flow.Launcher.Plugin.ShortcutPlugin/Services/CommandsService.cs +++ b/Flow.Launcher.Plugin.ShortcutPlugin/Services/CommandsService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Flow.Launcher.Plugin.ShortcutPlugin.Models.Shortcuts; using Flow.Launcher.Plugin.ShortcutPlugin.Repositories.Interfaces; using Flow.Launcher.Plugin.ShortcutPlugin.Services.Interfaces; @@ -31,9 +32,18 @@ public List ResolveCommand(List arguments, Query query) //TODO: Move this to different place? results.ForEach(result => { - result.AutoCompleteText = string.IsNullOrEmpty(result.AutoCompleteText) - ? $"{query.ActionKeyword} {result.Title}" - : $"{query.ActionKeyword} {result.AutoCompleteText}"; + if (string.IsNullOrEmpty(result.AutoCompleteText)) + { + result.AutoCompleteText = $"{query.ActionKeyword} {result.Title}"; + } + else if (result.ContextData is Shortcut) + { + result.AutoCompleteText = result.SubTitle; + } + else + { + result.AutoCompleteText = $"{query.ActionKeyword} {result.AutoCompleteText}"; + } }); return results; @@ -45,4 +55,4 @@ public void ReloadPluginData() _shortcutsService.Reload(); _variablesService.Reload(); } -} +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Services/ShortcutsService.cs b/Flow.Launcher.Plugin.ShortcutPlugin/Services/ShortcutsService.cs index 8a6b86e..d722f1f 100644 --- a/Flow.Launcher.Plugin.ShortcutPlugin/Services/ShortcutsService.cs +++ b/Flow.Launcher.Plugin.ShortcutPlugin/Services/ShortcutsService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Flow.Launcher.Plugin.ShortcutPlugin.Extensions; using Flow.Launcher.Plugin.ShortcutPlugin.Models.Shortcuts; @@ -40,11 +41,12 @@ public List GetShortcuts(List arguments) .Select(shortcut => { return ResultExtensions.Result( - shortcut.Key, + shortcut.GetTitle(), $"{shortcut}", () => { _shortcutHandler.ExecuteShortcut(shortcut, arguments); }, contextData: shortcut, - iconPath: shortcut.GetIcon() + iconPath: GetIcon(shortcut), + autoCompleteText: $"{shortcut}" ); }) .ToList(); @@ -73,7 +75,7 @@ public List GetGroups() .Select(group => { return ResultExtensions.Result( - group.Key, + group.GetTitle(), $"{group}", () => { _shortcutHandler.ExecuteShortcut(group, null); } ); @@ -105,7 +107,7 @@ private List RemoveShortcut(List shortcuts) return shortcuts.Select(shortcut => { return ResultExtensions.Result( - string.Format(Resources.ShortcutsManager_RemoveShortcut_Remove_shortcut, shortcut.Key), + string.Format(Resources.ShortcutsManager_RemoveShortcut_Remove_shortcut, shortcut.GetTitle()), shortcut.ToString(), () => { _shortcutsRepository.RemoveShortcut(shortcut); } ); @@ -122,16 +124,6 @@ public List OpenShortcuts(string key, IEnumerable arguments) return ResultExtensions.EmptyResult(); } - /*TODO: add group shortcut validation need to expand variables/arguments - if (!Validators.Validators.IsValidShortcut(shortcut)) - { - return ResultExtensions.SingleResult( - $"Shortcut with key '{key}' is not valid.", - "Please check the shortcut and try again.", - titleHighlightData: Enumerable.Range(19, key.Length).ToList() - ); - }*/ - var args = arguments.ToList(); var results = new List(); @@ -144,15 +136,15 @@ public List OpenShortcuts(string key, IEnumerable arguments) } var temp = $"Open {shortcut.GetDerivedType().ToLower()} "; - var defaultKey = $"{temp}{shortcut.Key}"; - var highlightIndexes = Enumerable.Range(temp.Length, shortcut.Key.Length).ToList(); + var defaultKey = $"{temp}{shortcut.GetTitle()}"; + var highlightIndexes = Enumerable.Range(temp.Length, shortcut.GetTitle().Length).ToList(); results.Add( BuildResult( shortcut, args, defaultKey, - shortcut.GetIcon(), + GetIcon(shortcut), titleHighlightData: highlightIndexes ) ); @@ -171,22 +163,22 @@ public IEnumerable OpenShortcut(Shortcut shortcut, IEnumerable a var args = arguments.ToList(); var results = new List(); - // if (shortcut is GroupShortcut groupShortcut) - // { - // results.AddRange(GetGroupShortcutResults(groupShortcut, args)); - // continue; - // } + /*if (shortcut is GroupShortcut groupShortcut) + { + results.AddRange(GetGroupShortcutResults(groupShortcut, args)); + return results; + }*/ var temp = $"Open {shortcut.GetDerivedType().ToLower()} "; - var defaultKey = $"{temp}{shortcut.Key}"; - var highlightIndexes = Enumerable.Range(temp.Length, shortcut.Key.Length).ToList(); + var defaultKey = $"{temp}{shortcut.GetTitle()}"; + var highlightIndexes = Enumerable.Range(temp.Length, shortcut.GetTitle().Length).ToList(); results.Add( BuildResult( shortcut, args, defaultKey, - shortcut.GetIcon(), + GetIcon(shortcut), titleHighlightData: highlightIndexes ) ); @@ -205,7 +197,7 @@ public List DuplicateShortcut(string existingKey, string newKey) return existingShortcuts.Select(shortcut => ResultExtensions.Result( - $"Duplicate {shortcut.GetDerivedType()} shortcut '{shortcut.Key}' to '{newKey}'", + $"Duplicate {shortcut.GetDerivedType()} shortcut '{shortcut.GetTitle()}' to '{newKey}'", shortcut.ToString(), () => { _shortcutsRepository.DuplicateShortcut(shortcut, newKey); })) .ToList(); @@ -325,8 +317,8 @@ List arguments var joinedArguments = string.Join(" ", arguments); var title = "Open group "; - var highlightIndexes = Enumerable.Range(title.Length, groupShortcut.Key.Length).ToList(); - title += groupShortcut.Key; + var highlightIndexes = Enumerable.Range(title.Length, groupShortcut.GetTitle().Length).ToList(); + title += groupShortcut.GetTitle(); var results = new List { @@ -339,7 +331,7 @@ List arguments _shortcutHandler.ExecuteShortcut(groupShortcut, arguments); return true; }, - IcoPath = groupShortcut.GetIcon(), + IcoPath = GetIcon(groupShortcut), TitleHighlightData = highlightIndexes, Score = 100 } @@ -354,14 +346,14 @@ List arguments return new Result { - Title = $"{shortcut.Key ?? shortcut.GetDerivedType()}", + Title = $"{shortcut.GetTitle() ?? shortcut.GetDerivedType()}", SubTitle = expandedShortcut, Action = _ => { _shortcutHandler.ExecuteShortcut(shortcut, arguments); return true; }, - IcoPath = shortcut.GetIcon(), + IcoPath = GetIcon(shortcut), ContextData = shortcut }; }) @@ -413,14 +405,14 @@ List arguments results.Add(new Result { - Title = $"{shortcut.Key ?? shortcut.GetDerivedType()}", + Title = $"{shortcut.GetTitle() ?? shortcut.GetDerivedType()}", SubTitle = expandedShortcut, Action = _ => { _shortcutHandler.ExecuteShortcut(shortcut, arguments); return true; }, - IcoPath = shortcut.GetIcon(), + IcoPath = GetIcon(shortcut), ContextData = shortcut }); } @@ -446,7 +438,8 @@ private Result BuildResult( var expandedShortcut = ExpandShortcut(shortcut, arguments); return ResultExtensions.Result( - (string.IsNullOrEmpty(defaultKey) ? shortcut.GetDerivedType() : defaultKey) + " ", // FIXME: Wrong order without space + (string.IsNullOrEmpty(defaultKey) ? shortcut.GetDerivedType() : defaultKey) + + " ", // FIXME: Wrong order without space expandedShortcut, () => { _shortcutHandler.ExecuteShortcut(shortcut, arguments); }, contextData: shortcut, @@ -462,4 +455,66 @@ private Result BuildResult( } ); } + + private string GetIcon(Shortcut shortcut) + { + if (!string.IsNullOrEmpty(shortcut.Icon)) + { + return shortcut.Icon; + } + + var arguments = new Dictionary(); + + switch (shortcut) + { + case FileShortcut fileShortcut: + { + var path = _variablesService.ExpandVariables(fileShortcut.Path, arguments); + + return string.IsNullOrEmpty(fileShortcut.App) + ? path + : AppUtilities.GetApplicationPath(fileShortcut.App); + } + case DirectoryShortcut directoryShortcut: + { + return _variablesService.ExpandVariables(directoryShortcut.Path, arguments); + } + case UrlShortcut urlShortcut: + { + if (!(urlShortcut.Url.Contains("www.") || urlShortcut.Url.Contains("http") || + urlShortcut.Url.Contains("https"))) + { + return AppUtilities.GetApplicationPath(urlShortcut.Url.Split(':')[0]); + } + + if (string.IsNullOrEmpty(urlShortcut.App)) + { + return AppUtilities.GetSystemDefaultBrowser(); + } + + if (Path.Exists(urlShortcut.App)) + { + return urlShortcut.App; + } + + var path = AppUtilities.GetApplicationPath(urlShortcut.App); + + return !string.IsNullOrEmpty(path) ? path : Icons.Link; + } + + case ShellShortcut shellShortcut: + { + return shellShortcut.ShellType switch + { + ShellType.Cmd => Icons.WindowsTerminal, + ShellType.Powershell => Icons.PowerShellBlack, + _ => Icons.Terminal + }; + } + case GroupShortcut: + return Icons.TabGroup; + default: + return Icons.Logo; + } + } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Utilities/AppUtilities.cs b/Flow.Launcher.Plugin.ShortcutPlugin/Utilities/AppUtilities.cs new file mode 100644 index 0000000..6a50742 --- /dev/null +++ b/Flow.Launcher.Plugin.ShortcutPlugin/Utilities/AppUtilities.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using Microsoft.Win32; + +namespace Flow.Launcher.Plugin.ShortcutPlugin.Utilities; + +public static class AppUtilities +{ + public static string GetApplicationPath(string appName) + { + if (Path.Exists(appName)) + { + return appName; + } + + if (appName != null && !appName.EndsWith(".exe")) + { + appName += ".exe"; + } + + using var key = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, ""); + using var subKey = key.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\" + appName); + + var path = subKey?.GetValue("Path"); + + if (path != null) + { + return (string) path + "\\" + appName; + } + + return ""; + } + + public static string GetSystemDefaultApp(string extension) + { + string name; + RegistryKey regKey = null; + + try + { + var regDefault = Registry.CurrentUser.OpenSubKey( + $"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\{extension}\\UserChoice", false); + var stringDefault = regDefault?.GetValue("ProgId"); + + regKey = Registry.ClassesRoot.OpenSubKey(stringDefault + "\\shell\\open\\command", false); + name = regKey?.GetValue(null)?.ToString()?.ToLower().Replace("" + (char) 34, ""); + + if (name != null && !name.EndsWith("exe")) + { + name = name[..(name.LastIndexOf(".exe", StringComparison.Ordinal) + 4)]; + } + } + catch (Exception ex) + { + name = ""; + } + finally + { + regKey?.Close(); + } + + return name; + } + + public static string GetSystemDefaultBrowser() + { + using var key = Registry.CurrentUser.OpenSubKey( + @"SOFTWARE\Microsoft\Windows\Shell\Associations\URLAssociations\http\UserChoice"); + + var s = (string) key?.GetValue("ProgId"); + + using var command = Registry.ClassesRoot.OpenSubKey($"{s}\\shell\\open\\command"); + + return (string) command?.GetValue(null); + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/Utilities/Icons.cs b/Flow.Launcher.Plugin.ShortcutPlugin/Utilities/Icons.cs index 14310e6..fda4023 100644 --- a/Flow.Launcher.Plugin.ShortcutPlugin/Utilities/Icons.cs +++ b/Flow.Launcher.Plugin.ShortcutPlugin/Utilities/Icons.cs @@ -32,13 +32,15 @@ public static class Icons public const string Terminal = "Images\\terminal.png"; + public const string Shell = "Images\\shell.png"; + public const string Web = "Images\\web.png"; public const string VisualCode = "Images\\vscode.png"; public const string VisualCodeInsiders = "Images\\vscode-insiders.png"; - public const string PowerShell = "Images\\powershell.png"; + public const string PowerShellBlack = "Images\\powershell_black.png"; public const string WindowsTerminal = "Images\\windows-terminal.png"; diff --git a/Flow.Launcher.Plugin.ShortcutPlugin/plugin.json b/Flow.Launcher.Plugin.ShortcutPlugin/plugin.json index b9297ed..f14e870 100644 --- a/Flow.Launcher.Plugin.ShortcutPlugin/plugin.json +++ b/Flow.Launcher.Plugin.ShortcutPlugin/plugin.json @@ -3,11 +3,11 @@ "Name": "Shortcuts", "Description": "Open user defined shortcut quickly from Flow Launcher", "Author": "Mantelis", - "Version": "1.1.7", + "Version": "1.1.8", "Language": "csharp", "ActionKeyword": "q", "Website": "https://github.com/mantasjasikenas/flow-launcher-shortcuts-plugin", - "UrlDownload":"https://github.com/mantasjasikenas/flow-launcher-shortcuts-plugin/releases/download/v1.1.7/Flow.Launcher.Plugin.ShortcutPlugin.zip", + "UrlDownload":"https://github.com/mantasjasikenas/flow-launcher-shortcuts-plugin/releases/download/v1.1.8/Flow.Launcher.Plugin.ShortcutPlugin.zip", "UrlSourceCode": "https://github.com/mantasjasikenas/flow-launcher-shortcuts-plugin/tree/master", "IcoPath": "images\\icon.png", "ExecuteFileName": "Flow.Launcher.Plugin.ShortcutPlugin.dll" diff --git a/README.md b/README.md index 987ca4a..cca4918 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@
- Shortcuts logo -

Shortcuts Plugin
Quickly launch your shortcuts

+ Shortcuts logo + +

Shortcuts Plugin
Quickly launch your shortcuts


@@ -102,7 +103,8 @@ The following shortcut types are available. More types will be added in the futu If you want to add a new shortcut, you can use the following commands. Keep in mind that if shortcut contains spaces, you should use quotes around the arguments. Also, if you need to use quotes in the arguments, you should escape them. -You can do that by using `\"` or using Unicode escape sequence `\u0022`. For example, `\"C:\Program Files\MyApp\app.exe\"` +You can do that by using `\"` or using Unicode escape sequence `\u0022`. For example, +`\"C:\Program Files\MyApp\app.exe\"` - Directory shortcut @@ -248,6 +250,9 @@ syntax. Valid variable object attributes are `Name` and `Value`.

![Commands](assets/screenshots/commands_2.png) +
+
+![Commands](assets/screenshots/commands_3.png) # Licence diff --git a/assets/screenshots/commands_1.png b/assets/screenshots/commands_1.png index 9fac5b6..4de8f74 100644 Binary files a/assets/screenshots/commands_1.png and b/assets/screenshots/commands_1.png differ diff --git a/assets/screenshots/commands_2.png b/assets/screenshots/commands_2.png index fef976e..b984230 100644 Binary files a/assets/screenshots/commands_2.png and b/assets/screenshots/commands_2.png differ diff --git a/assets/screenshots/commands_3.png b/assets/screenshots/commands_3.png new file mode 100644 index 0000000..c0e369d Binary files /dev/null and b/assets/screenshots/commands_3.png differ diff --git a/build/release.ps1 b/build/release.ps1 index ee1ba56..2e594f6 100644 --- a/build/release.ps1 +++ b/build/release.ps1 @@ -5,31 +5,8 @@ param ( [string]$configuration = "Release" ) -function Get-PropertyFromJson -{ - param ( - [string]$jsonFilePath, - [string]$propertyName - ) - - # Read the JSON file content - $jsonContent = Get-Content -Path $pluginJson -Raw - - # Parse the JSON content - $jsonObject = ConvertFrom-Json -InputObject $jsonContent - - # Return the extracted property - return $jsonObject.$propertyName -} - -function Remove-Directory($path) -{ - if (Test-Path $path) - { - Remove-Item -Force -Recurse -Path "$path\*" - } -} - +# Load functions +. "$PSScriptRoot\utils.ps1" # Define variables $parentDirectory = Split-Path -Path $PSScriptRoot -Parent @@ -43,16 +20,18 @@ $desktopDest = Join-Path -Path $userProfileDir -ChildPath "Desktop\$pluginName-$ Write-Host "Plugin $pluginName-$version publish" -ForegroundColor Magenta Write-Host -Write-Host "Script started..." -ForegroundColor Yellow +Print-Normal "Script started..." # Stop Flow Launcher -Write-Host "Stopping Flow Launcher..." -ForegroundColor Yellow +Print-Normal "Stopping Flow Launcher..." Stop-Process -Name "Flow.Launcher" -Force +Wait-Process -Name "Flow.Launcher" +Start-Sleep -Milliseconds 500 # Clean up directories -Write-Host "Cleaning up directories..." -ForegroundColor Yellow +Print-Normal "Cleaning up directories..." Remove-Directory -Path $publishDest Remove-Directory -Path $desktopDest $directoriesToRemove = Get-ChildItem -Path $pluginsDirectory -Directory | Where-Object { $_.Name -like "$pluginName-*" } @@ -65,12 +44,12 @@ foreach ($directory in $directoriesToRemove) # Publish plugin if ($configuration -eq "Debug") { - Write-Host "Building and publishing plugin in Debug mode..." -ForegroundColor Yellow + Print-Normal "Building and publishing plugin in Debug mode..." $publish = dotnet publish "Flow.Launcher.Plugin.ShortcutPlugin" -c Debug -r win-x64 --no-self-contained -o $publishDest } else { - Write-Host "Building and publishing plugin in Release mode..." -ForegroundColor Yellow + Print-Normal "Building and publishing plugin in Release mode..." $publish = dotnet publish "Flow.Launcher.Plugin.ShortcutPlugin" -c Release -r win-x64 --no-self-contained -o $publishDest } @@ -78,27 +57,27 @@ $publish_result = $LASTEXITCODE if ($publish_result -ne 0) { - Write-Host "Publish failed with exit code $publish_result" -ForegroundColor Red + Print-Error "Publish failed with exit code $publish_result" exit $publish_result } # Copy plugin to destination -Write-Host "Copying plugin to destination..." -ForegroundColor Yellow +Print-Normal "Copying plugin to destination..." Copy-Item -Path $publishDest -Destination $pluginDest -Force -Recurse # Start Flow Launcher -Write-Host "Starting Flow Launcher..." -ForegroundColor Yellow +Print-Normal "Starting Flow Launcher..." Start-Process (Join-Path -Path $userProfileDir -ChildPath "AppData\Local\FlowLauncher\Flow.Launcher.exe") # Process publish to desktop if ($copyToDesktop) { - Write-Host "Copying plugin to desktop..." -ForegroundColor Yellow + Print-Normal "Copying plugin to desktop..." Copy-Item -Path $publishDest -Destination $desktopDest -Force -Recurse Compress-Archive -Path $desktopDest -DestinationPath "$desktopDest.zip" -Force } Write-Host -Write-Host "Script finished. Success!" -ForegroundColor Green \ No newline at end of file +Print-Success "Script finished. Success!" \ No newline at end of file diff --git a/build/utils.ps1 b/build/utils.ps1 new file mode 100644 index 0000000..01a8591 --- /dev/null +++ b/build/utils.ps1 @@ -0,0 +1,61 @@ +function Get-PropertyFromJson +{ + param ( + [string]$jsonFilePath, + [string]$propertyName + ) + + # Read the JSON file content + $jsonContent = Get-Content -Path $pluginJson -Raw + + # Parse the JSON content + $jsonObject = ConvertFrom-Json -InputObject $jsonContent + + # Return the extracted property + return $jsonObject.$propertyName +} + +function Remove-Directory($path) +{ + if (Test-Path $path) + { + Remove-Item -Force -Recurse -Path "$path\*" + } +} + +function Print-Message +{ + param ( + [string]$message, + [string]$color + ) + + Write-Host $message -ForegroundColor $color +} + +function Print-Success +{ + param ( + [string]$message + ) + + Print-Message -message $message -color Green +} + +function Print-Error +{ + param ( + [string]$message + ) + + Print-Message -message $message -color Red +} + +function Print-Normal +{ + param ( + [string]$message + ) + + Print-Message -message $message -color White +} \ No newline at end of file