Initial commit to new repository
This commit is contained in:
204
src/AxCopilot/Handlers/FavoriteHandler.cs
Normal file
204
src/AxCopilot/Handlers/FavoriteHandler.cs
Normal file
@@ -0,0 +1,204 @@
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Themes;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 즐겨찾기(파일/폴더/경로) 핸들러. "fav" 프리픽스로 사용합니다.
|
||||
/// 자주 사용하는 로컬 파일·폴더 경로를 등록하고 빠르게 실행합니다.
|
||||
/// 예: fav → 전체 즐겨찾기 목록
|
||||
/// fav 보고서 → "보고서" 포함 항목 필터
|
||||
/// fav add 보고서 C:\work\report.xlsx → 즐겨찾기 추가
|
||||
/// fav del 보고서 → 즐겨찾기 삭제
|
||||
/// Enter → 파일/폴더 열기.
|
||||
/// </summary>
|
||||
public class FavoriteHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "fav";
|
||||
|
||||
public PluginMetadata Metadata => new(
|
||||
"Favorite",
|
||||
"즐겨찾기 관리 — fav",
|
||||
"1.0",
|
||||
"AX");
|
||||
|
||||
private static readonly string FavFile = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"AxCopilot", "favorites.json");
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOpts = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
private List<FavEntry> _cache = new();
|
||||
private bool _loaded;
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
EnsureLoaded();
|
||||
var q = query.Trim();
|
||||
|
||||
// add 명령
|
||||
if (q.StartsWith("add ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var rest = q[4..].Trim();
|
||||
var spaceIdx = rest.IndexOf(' ');
|
||||
if (spaceIdx > 0)
|
||||
{
|
||||
var name = rest[..spaceIdx].Trim();
|
||||
var path = rest[(spaceIdx + 1)..].Trim();
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem(
|
||||
$"즐겨찾기 추가: {name}",
|
||||
$"경로: {path} · Enter로 추가",
|
||||
null, ValueTuple.Create("__ADD__", name, path),
|
||||
Symbol: Symbols.Save)
|
||||
]);
|
||||
}
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem(
|
||||
"사용법: fav add [이름] [경로]",
|
||||
"예: fav add 보고서 C:\\work\\report.xlsx",
|
||||
null, null, Symbol: Symbols.Info)
|
||||
]);
|
||||
}
|
||||
|
||||
// del 명령
|
||||
if (q.StartsWith("del ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var name = q[4..].Trim();
|
||||
var found = _cache.FirstOrDefault(b =>
|
||||
b.Name.Contains(name, StringComparison.OrdinalIgnoreCase));
|
||||
if (found != null)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem(
|
||||
$"즐겨찾기 삭제: {found.Name}",
|
||||
$"경로: {found.Path} · Enter로 삭제",
|
||||
null, ValueTuple.Create("__DEL__", found.Name),
|
||||
Symbol: Symbols.Delete)
|
||||
]);
|
||||
}
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem($"'{name}'에 해당하는 즐겨찾기 없음", "fav으로 전체 목록 확인",
|
||||
null, null, Symbol: Symbols.Warning)
|
||||
]);
|
||||
}
|
||||
|
||||
// 목록/검색
|
||||
var filtered = string.IsNullOrWhiteSpace(q)
|
||||
? _cache
|
||||
: _cache.Where(b =>
|
||||
b.Name.Contains(q, StringComparison.OrdinalIgnoreCase) ||
|
||||
b.Path.Contains(q, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
|
||||
if (!filtered.Any())
|
||||
{
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem(
|
||||
_cache.Count == 0 ? "즐겨찾기가 없습니다" : $"'{q}'에 해당하는 즐겨찾기 없음",
|
||||
"fav add [이름] [경로]로 추가하세요",
|
||||
null, null, Symbol: Symbols.Info)
|
||||
]);
|
||||
}
|
||||
|
||||
var items = filtered.Select(b =>
|
||||
{
|
||||
var isDir = Directory.Exists(b.Path);
|
||||
var isFile = File.Exists(b.Path);
|
||||
var symbol = isDir ? Symbols.Folder : isFile ? Symbols.File : Symbols.Warning;
|
||||
var hint = isDir ? "폴더 열기" : isFile ? "파일 열기" : "경로를 찾을 수 없음";
|
||||
return new LauncherItem(b.Name, $"{b.Path} · {hint}", null, b.Path, Symbol: symbol);
|
||||
}).ToList<LauncherItem>();
|
||||
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is ValueTuple<string, string, string> add && add.Item1 == "__ADD__")
|
||||
{
|
||||
AddFav(add.Item2, add.Item3);
|
||||
NotificationService.Notify("AX Copilot", $"즐겨찾기 추가: {add.Item2}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (item.Data is ValueTuple<string, string> del && del.Item1 == "__DEL__")
|
||||
{
|
||||
RemoveFav(del.Item2);
|
||||
NotificationService.Notify("AX Copilot", $"즐겨찾기 삭제: {del.Item2}");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (item.Data is string path)
|
||||
{
|
||||
if (Directory.Exists(path) || File.Exists(path))
|
||||
{
|
||||
try { Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }); }
|
||||
catch (Exception ex) { LogService.Warn($"즐겨찾기 열기 실패: {ex.Message}"); }
|
||||
}
|
||||
else
|
||||
{
|
||||
try { Application.Current?.Dispatcher.Invoke(() => Clipboard.SetText(path)); }
|
||||
catch (Exception) { }
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void EnsureLoaded()
|
||||
{
|
||||
if (_loaded) return;
|
||||
_loaded = true;
|
||||
try
|
||||
{
|
||||
if (!File.Exists(FavFile)) return;
|
||||
_cache = JsonSerializer.Deserialize<List<FavEntry>>(File.ReadAllText(FavFile), JsonOpts) ?? new();
|
||||
}
|
||||
catch (Exception ex) { LogService.Warn($"즐겨찾기 로드 실패: {ex.Message}"); }
|
||||
}
|
||||
|
||||
private void AddFav(string name, string path)
|
||||
{
|
||||
EnsureLoaded();
|
||||
_cache.RemoveAll(b => b.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
_cache.Insert(0, new FavEntry { Name = name, Path = path });
|
||||
Save();
|
||||
}
|
||||
|
||||
private void RemoveFav(string name)
|
||||
{
|
||||
EnsureLoaded();
|
||||
_cache.RemoveAll(b => b.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
Save();
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(FavFile)!);
|
||||
File.WriteAllText(FavFile, JsonSerializer.Serialize(_cache, JsonOpts));
|
||||
}
|
||||
catch (Exception ex) { LogService.Warn($"즐겨찾기 저장 실패: {ex.Message}"); }
|
||||
}
|
||||
|
||||
private class FavEntry
|
||||
{
|
||||
[JsonPropertyName("name")] public string Name { get; set; } = "";
|
||||
[JsonPropertyName("path")] public string Path { get; set; } = "";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user