[Phase L4-5] 고급 검색 필터 문법 구현 (ext/type/in/size/modified)
Core/SearchFilterParser.cs (285줄) — 신규: - Parse(): 쿼리에서 필터 토큰 추출 후 순수 텍스트 쿼리 반환 - 지원 필터: ext:.pdf,.docx / type:file|folder|app / in:경로조각 size:>1mb|<500kb / modified:today|week|month|year|>날짜 - Matches(): 8가지 조건(ext,type,in,size,modified) 순차 검사 size/modified는 파일시스템 접근이 필요해 마지막에 체크 - Describe(): UI 힌트용 필터 요약 텍스트 생성 - ParsedFilters 클래스: 파싱된 필터 상태 컨테이너 Core/FuzzyEngine.cs (+23줄): - SearchWithFilter(query, predicate, max): 텍스트 없으면 전체, 있으면 ×15 후보→필터 Core/CommandResolver.cs (+91줄): - ResolveAsync(): 경로 쿼리 감지 다음에 필터 감지 단계 추가 - BuildFilteredResults(): 필터 힌트 항목(상단) + 필터 적용 결과 목록 파일 항목에 size/수정일 부가 정보 subtitle 표시 - FormatBytes(): 파일 크기 포맷 (B/KB/MB/GB) - 빌드: 경고 0, 오류 0
This commit is contained in:
@@ -82,9 +82,16 @@ public class CommandResolver
|
||||
return await fb.GetItemsAsync(input, ct);
|
||||
}
|
||||
|
||||
// 3. Fuzzy 검색 폴백 + null-prefix 핸들러 병렬 실행
|
||||
// 3. 고급 필터 문법 감지 (ext:, size:, modified:, type:, in:)
|
||||
var maxResults = _settings.Settings.Launcher.MaxResults;
|
||||
var (cleanQuery, filters) = SearchFilterParser.Parse(input);
|
||||
|
||||
if (filters.HasFilters)
|
||||
{
|
||||
return BuildFilteredResults(cleanQuery, filters, maxResults);
|
||||
}
|
||||
|
||||
// 4. Fuzzy 검색 폴백 + null-prefix 핸들러 병렬 실행
|
||||
// Path 기반 중복 제거: 같은 경로의 항목이 여러 키워드로 매칭될 때 첫 번째만 표시
|
||||
var seenPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
// SortByUsage에 lazy 시퀀스를 직접 전달 → 중간 ToList 1회 제거
|
||||
@@ -204,6 +211,88 @@ public class CommandResolver
|
||||
|
||||
public IReadOnlyDictionary<string, IActionHandler> RegisteredHandlers => _handlers;
|
||||
|
||||
// ─── 고급 필터 검색 결과 생성 ─────────────────────────────────────────
|
||||
private IEnumerable<SDK.LauncherItem> BuildFilteredResults(
|
||||
string textQuery, ParsedFilters filters, int maxResults)
|
||||
{
|
||||
var results = new List<SDK.LauncherItem>();
|
||||
|
||||
// 필터 힌트 항목 (상단 표시 — 어떤 필터가 적용 중인지 안내)
|
||||
var hint = SearchFilterParser.Describe(filters);
|
||||
results.Add(new SDK.LauncherItem(
|
||||
$"필터 적용 중: {hint}",
|
||||
string.IsNullOrEmpty(textQuery)
|
||||
? "전체 항목에서 검색 중"
|
||||
: $"'{textQuery}' + 필터",
|
||||
null, null,
|
||||
Symbol: "\uE16E", // Filter 아이콘 (MDL2)
|
||||
Group: "필터"));
|
||||
|
||||
// 퍼지 + 필터 적용 (size/modified는 파일시스템 접근이 있으므로 Task.Run에서 실행)
|
||||
var filtered = _fuzzy.SearchWithFilter(
|
||||
textQuery,
|
||||
e => SearchFilterParser.Matches(e, filters),
|
||||
maxResults);
|
||||
|
||||
var seenPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var r in filtered)
|
||||
{
|
||||
if (!seenPaths.Add(r.Entry.Path)) continue;
|
||||
|
||||
// 크기/수정일 부가 정보 — 파일 항목에만 추가
|
||||
string sub = r.Entry.Path + " ⇧ Shift+Enter: 폴더 열기";
|
||||
if (r.Entry.Type == IndexEntryType.File &&
|
||||
(filters.SizeBytes != null || filters.ModifiedFrom != null))
|
||||
{
|
||||
try
|
||||
{
|
||||
var fi = new System.IO.FileInfo(
|
||||
Environment.ExpandEnvironmentVariables(r.Entry.Path));
|
||||
if (fi.Exists)
|
||||
{
|
||||
var sizeTxt = FormatBytes(fi.Length);
|
||||
var dateTxt = fi.LastWriteTime.ToString("yyyy-MM-dd");
|
||||
sub = $"{sizeTxt} · {dateTxt} · {r.Entry.Path}";
|
||||
}
|
||||
}
|
||||
catch { /* 접근 실패 시 기본 subtitle */ }
|
||||
}
|
||||
|
||||
results.Add(new SDK.LauncherItem(
|
||||
r.Entry.DisplayName,
|
||||
sub,
|
||||
null,
|
||||
r.Entry,
|
||||
Symbol: r.Entry.Type switch
|
||||
{
|
||||
IndexEntryType.App => Symbols.App,
|
||||
IndexEntryType.Folder => Symbols.Folder,
|
||||
_ => Symbols.File
|
||||
},
|
||||
Group: "검색 결과"));
|
||||
}
|
||||
|
||||
if (results.Count == 1) // 힌트 항목만 있음 → 결과 없음 메시지
|
||||
{
|
||||
results.Add(new SDK.LauncherItem(
|
||||
"검색 결과 없음",
|
||||
$"필터 조건을 확인하세요: {hint}",
|
||||
null, null,
|
||||
Symbol: Symbols.Error,
|
||||
Group: "검색 결과"));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static string FormatBytes(long b) => b switch
|
||||
{
|
||||
>= 1024L * 1024 * 1024 => $"{b / (1024.0 * 1024 * 1024):F1}GB",
|
||||
>= 1024L * 1024 => $"{b / (1024.0 * 1024):F1}MB",
|
||||
>= 1024L => $"{b / 1024.0:F1}KB",
|
||||
_ => $"{b}B"
|
||||
};
|
||||
|
||||
// null-prefix 핸들러 실행 (ExecuteAsync 라우팅)
|
||||
public async Task ExecuteNullPrefixAsync(SDK.LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user