Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
24a0e81
refactor: extract IItemRepository interface from ItemRepository
kafuexe Mar 14, 2026
5e9cfc6
refactor: decouple ItemValidator from SlickFlow plugin class
kafuexe Mar 14, 2026
05f7674
refactor: decouple command handlers and processor from SlickFlow plug…
kafuexe Mar 14, 2026
4fd5349
refactor: add InternalsVisibleTo for test project
kafuexe Mar 14, 2026
dac0458
refactor: make SettingsManager path-injectable for testability
kafuexe Mar 14, 2026
76c0a8e
chore: create xUnit test project with Moq and FluentAssertions
kafuexe Mar 14, 2026
dbb2ae2
test: add comprehensive unit and integration tests (90 tests)
kafuexe Mar 14, 2026
1faa1f4
ci: add GitHub Actions test workflow and AppVeyor test step
kafuexe Mar 14, 2026
8c28580
ci: add release workflow and normalize asset sizes to 32x32
kafuexe Mar 14, 2026
e77934a
feat: add search bar to filter items in settings panel
kafuexe Mar 31, 2026
5ae2b5a
fix textbox looks
kafuexe Mar 31, 2026
245099e
feat: add RunAs dropdown to item edit mode
kafuexe Mar 31, 2026
b1c5114
fix: resolve all CS build warnings
kafuexe Mar 31, 2026
bc87686
test: add 68 new tests covering ViewModels, Utils, and error paths
kafuexe Mar 31, 2026
27dab20
refactor: split monolithic IconHelper into focused classes
kafuexe Mar 31, 2026
2bea18b
feat: add meta items with cycle-safe alias chaining
kafuexe May 12, 2026
db4c99a
feat: parameterized items with sequential prompt UX
kafuexe May 12, 2026
d49b4bd
fix: max-score prompt-mode result so it dominates other plugins
kafuexe May 12, 2026
b471453
Icon resolve fixes
kafuexe May 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Release

on:
release:
types: [published]

jobs:
build-and-upload:
runs-on: windows-latest

steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
7.0.x
8.0.x

- name: Restore dependencies
run: dotnet restore

- name: Run tests
run: dotnet test --verbosity normal

- name: Publish
run: dotnet publish Flow.Launcher.Plugin.SlickFlow -c Release -r win-x64 --no-self-contained

- name: Create release zip
shell: pwsh
run: |
$publishDir = "Flow.Launcher.Plugin.SlickFlow/bin/Release/win-x64/publish"
$zipPath = "Flow.Launcher.Plugin.SlickFlow.zip"
Compress-Archive -Path "$publishDir/*" -DestinationPath $zipPath -Force

- name: Upload release asset
uses: softprops/action-gh-release@v2
with:
files: Flow.Launcher.Plugin.SlickFlow.zip
30 changes: 30 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Tests

on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]

jobs:
test:
runs-on: windows-latest

steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
7.0.x
8.0.x

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore

- name: Test
run: dotnet test --no-build --verbosity normal
Binary file modified Flow.Launcher.Plugin.SlickFlow/Assets/Graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Flow.Launcher.Plugin.SlickFlow/Assets/IncognitoIcon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Flow.Launcher.Plugin.SlickFlow/Assets/Powershell.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Flow.Launcher.Plugin.SlickFlow/Assets/Shield.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Flow.Launcher.Plugin.SlickFlow/Assets/Tag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Flow.Launcher.Plugin.SlickFlow/Assets/Terminal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
using Flow.Launcher.Plugin.SlickFlow.Items;
using Flow.Launcher.Plugin.SlickFlow.Items.Abstract;
using Flow.Launcher.Plugin.SlickFlow.items;

namespace Flow.Launcher.Plugin.SlickFlow.Commands.CommandHandlers;

public class AddCommandHandler : ICommandHandler
{
private readonly SlickFlow _plugin;
private readonly IItemRepository _itemRepo;
private readonly ItemValidator _itemValidator;
private readonly string _slickFlowIcon;

public AddCommandHandler(SlickFlow plugin)
public AddCommandHandler(IItemRepository itemRepo, ItemValidator itemValidator, string slickFlowIcon)
{
_plugin = plugin;
_itemRepo = itemRepo;
_itemValidator = itemValidator;
_slickFlowIcon = slickFlowIcon;
}

public List<Result> Handle(string[] args)
Expand All @@ -22,7 +28,7 @@ public List<Result> Handle(string[] args)
{
Title = "Usage: add <alias1|alias2> <file-or-url> [args...] [runas]",
Score = int.MaxValue - 1000,
IcoPath = _plugin._slickFlowIcon
IcoPath = _slickFlowIcon
});
return results;
}
Expand Down Expand Up @@ -60,7 +66,7 @@ public List<Result> Handle(string[] args)
}

// Prevent duplicate aliases
var validationResults = _plugin._itemValidator.ValidateAliases(aliases);
var validationResults = _itemValidator.ValidateAliases(aliases);
if (validationResults.Any())
{
return validationResults;
Expand All @@ -72,7 +78,7 @@ public List<Result> Handle(string[] args)
Title = $"Add item: {string.Join(", ", aliases)}",
SubTitle = $"File: {fileOrUrl} {fileArgs}".Trim(),
Score = int.MaxValue - 1000,
IcoPath = _plugin._slickFlowIcon,
IcoPath = _slickFlowIcon,
Action = _ =>
{
var item = new Item
Expand All @@ -84,12 +90,12 @@ public List<Result> Handle(string[] args)
};

// Add the item to the repository
_plugin._itemRepo.AddItem(item);
_itemRepo.AddItem(item);

return true;
}
});

return results;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
using Flow.Launcher.Plugin.SlickFlow.Items;
using Flow.Launcher.Plugin.SlickFlow.Items.Abstract;
using Flow.Launcher.Plugin.SlickFlow.items;

namespace Flow.Launcher.Plugin.SlickFlow.Commands.CommandHandlers;

public class AliasCommandHandler : ICommandHandler
{
private readonly SlickFlow _plugin;
private readonly IItemRepository _itemRepo;
private readonly ItemValidator _itemValidator;
private readonly string _slickFlowIcon;

public AliasCommandHandler(SlickFlow plugin)
public AliasCommandHandler(IItemRepository itemRepo, ItemValidator itemValidator, string slickFlowIcon)
{
_plugin = plugin;
_itemRepo = itemRepo;
_itemValidator = itemValidator;
_slickFlowIcon = slickFlowIcon;
}

public List<Result> Handle(string[] args)
Expand All @@ -21,17 +27,17 @@ public List<Result> Handle(string[] args)
{
Title = "Usage: alias <existing-alias-or-id> <newAlias1|newAlias2>",
Score = int.MaxValue - 1000,
IcoPath = _plugin._slickFlowIcon
IcoPath = _slickFlowIcon
});
return results;
}

string target = args[0];
Item? item = _plugin._itemRepo.GetItemById(target) ?? _plugin._itemRepo.GetItemByAlias(target);
Item? item = _itemRepo.GetItemById(target) ?? _itemRepo.GetItemByAlias(target);
if (item == null)
{
results.Add(new Result { Title = $"No item found with '{target}'",
IcoPath = _plugin._slickFlowIcon, Score = int.MaxValue - 1000 });
IcoPath = _slickFlowIcon, Score = int.MaxValue - 1000 });
return results;
}

Expand All @@ -40,7 +46,7 @@ public List<Result> Handle(string[] args)
.Where(a => !string.IsNullOrWhiteSpace(a))
.ToList();

var validationResults = _plugin._itemValidator.ValidateAliases(newAliases);
var validationResults = _itemValidator.ValidateAliases(newAliases);
if (validationResults.Any())
{
return validationResults;
Expand All @@ -51,7 +57,7 @@ public List<Result> Handle(string[] args)
Title = $"Add {newAliases.Count} alias(es) to item {item.Id}",
SubTitle = $"Existing aliases: {string.Join(", ", item.Aliases)}",
Score = int.MaxValue - 1000,
IcoPath = _plugin._slickFlowIcon,
IcoPath = _slickFlowIcon,
Action = _ =>
{
int addedCount = 0;
Expand All @@ -65,12 +71,12 @@ public List<Result> Handle(string[] args)
}

if (addedCount > 0)
_plugin._itemRepo.UpdateItem(item);
_itemRepo.UpdateItem(item);

return true;
}
});

return results;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
using Flow.Launcher.Plugin.SlickFlow.Items;
using Flow.Launcher.Plugin.SlickFlow.Items.Abstract;

namespace Flow.Launcher.Plugin.SlickFlow.Commands.CommandHandlers;

public class DeleteCommandHandler : ICommandHandler
{
private readonly SlickFlow _plugin;
private readonly IItemRepository _itemRepo;
private readonly string _slickFlowIcon;

public DeleteCommandHandler(SlickFlow plugin)
public DeleteCommandHandler(IItemRepository itemRepo, string slickFlowIcon)
{
_plugin = plugin;
_itemRepo = itemRepo;
_slickFlowIcon = slickFlowIcon;
}

public List<Result> Handle(string[] args)
Expand All @@ -21,18 +24,18 @@ public List<Result> Handle(string[] args)
{
Title = "Usage: delete <alias-or-id>",
Score = int.MaxValue - 1000,
IcoPath = _plugin._slickFlowIcon
IcoPath = _slickFlowIcon
});
return results;
}

string target = args[0];
Item? item = _plugin._itemRepo.GetItemById(target) ?? _plugin._itemRepo.GetItemByAlias(target);
Item? item = _itemRepo.GetItemById(target) ?? _itemRepo.GetItemByAlias(target);


if (item == null)
{
results.Add(new Result { Title = $"No item found with '{target}'", IcoPath = _plugin._slickFlowIcon, Score = int.MaxValue - 1000 });
results.Add(new Result { Title = $"No item found with '{target}'", IcoPath = _slickFlowIcon, Score = int.MaxValue - 1000 });
return results;
}

Expand All @@ -42,15 +45,15 @@ public List<Result> Handle(string[] args)
Title = $"Confirm delete of item {item.Id}?",
Score = int.MaxValue - 1000,
SubTitle = $"Aliases: {string.Join(", ", item.Aliases)}",
IcoPath = _plugin._slickFlowIcon,
IcoPath = _slickFlowIcon,
Action = _ =>
{
_plugin._itemRepo.DeleteItem(item.Id);
_itemRepo.DeleteItem(item.Id);
Console.WriteLine($"[Deleted] Item {item.Id} ({item.FileName})");
return true;
}
});

return results;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
using Flow.Launcher.Plugin.SlickFlow.Items;
using Flow.Launcher.Plugin.SlickFlow.Items.Abstract;

namespace Flow.Launcher.Plugin.SlickFlow.Commands.CommandHandlers;

public class RemoveCommandHandler : ICommandHandler
{
private readonly SlickFlow _plugin;
private readonly IItemRepository _itemRepo;
private readonly string _slickFlowIcon;

public RemoveCommandHandler(SlickFlow plugin)
public RemoveCommandHandler(IItemRepository itemRepo, string slickFlowIcon)
{
_plugin = plugin;
_itemRepo = itemRepo;
_slickFlowIcon = slickFlowIcon;
}

public List<Result> Handle(string[] args)
Expand All @@ -18,20 +23,20 @@ public List<Result> Handle(string[] args)
{
Title = "Usage: remove <alias>",
Score = int.MaxValue - 1000,
IcoPath = _plugin._slickFlowIcon
IcoPath = _slickFlowIcon
});
return results;
}

var alias = args[0];
var item = _plugin._itemRepo.GetItemByAlias(alias);
var item = _itemRepo.GetItemByAlias(alias);
if (item == null)
{
results.Add(new Result
{
Title = $"No item found with alias '{alias}'",
Score = int.MaxValue - 1000,
IcoPath = _plugin._slickFlowIcon
IcoPath = _slickFlowIcon
});
return results;
}
Expand All @@ -42,7 +47,7 @@ public List<Result> Handle(string[] args)
{
Title = $"Item only has one alias. Use 'delete {alias}' to delete the item instead.",
Score = int.MaxValue - 1000,
IcoPath = _plugin._slickFlowIcon
IcoPath = _slickFlowIcon
});
return results;
}
Expand All @@ -51,13 +56,13 @@ public List<Result> Handle(string[] args)
{
Title = $"Remove alias '{alias}' from item {item.Id}",
Score = int.MaxValue - 1000,
IcoPath = _plugin._slickFlowIcon,
IcoPath = _slickFlowIcon,
Action = _ =>
{
_plugin._itemRepo.RemoveAlias(item.Id, alias);
_itemRepo.RemoveAlias(item.Id, alias);
return true;
}
});
return results;
}
}
}
Loading
Loading