[Phase L4-4/L4-6] 검색 히스토리 + 계산기 단위변환 단축 문법
L4-4 검색 히스토리 (↑/↓ 키 탐색): - Services/SearchHistoryService.cs (신규 100줄): 50개 FIFO JSON 저장 Add() · GetAll() · Clear(). 2자 미만/중복 최상단 무시 - ViewModels/LauncherViewModel.cs: _historyIndex·_isHistoryNavigation 필드 추가 NavigateHistoryPrev() / NavigateHistoryNext() / SetInputFromHistory() InputText setter: 직접 입력 시 _historyIndex 초기화 ExecuteSelectedAsync: 실행 전 히스토리 저장 (2자 이상) OnShown: _historyIndex = -1 초기화 - Views/LauncherWindow.Keyboard.cs: Key.Up/Down — Results.Count==0 분기: 히스토리 탐색 / 목록 탐색 분기 L4-3 클립보드 핀/카테고리: 기존 완전 구현 확인 (IsPinned, Category, TogglePin, Ctrl+P, #pin/#url/#코드/#경로 필터) L4-6 계산기 단위 변환 단축 문법: - Handlers/UnitConverter.cs: AutoSuggest(): "20km", "100f", "5lb" 등 목표 없이 주요 단위 자동 제안 _suggestions 테이블: 길이/무게/속도/데이터/온도 40개 단위 매핑 DateShortcut: "today+30d", "today-7w" → = 접두어에서 날짜 계산 - Handlers/CalculatorHandler.cs: DateShortcut.TryMatch 분기 추가 (통화 감지 전) UnitConverter.AutoSuggest 분기 추가 (명시 변환 후) - 힌트 텍스트: "20km · 100°F · today+30d" 추가 docs/LAUNCHER_ROADMAP.md: Phase L4 계획 테이블 추가 빌드: 경고 0, 오류 0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -40,13 +40,17 @@ public class CalculatorHandler : IActionHandler
|
||||
|
||||
var trimmed = query.Trim();
|
||||
|
||||
// ─── today+Nd / today-Nd 날짜 단축 계산 (= 접두어에서 date 핸들러 위임) ─
|
||||
if (DateShortcut.TryMatch(trimmed, out var dateItems))
|
||||
return dateItems!;
|
||||
|
||||
// ─── 통화 변환 우선 감지 ──────────────────────────────────────────────
|
||||
if (CurrencyConverter.IsCurrencyQuery(trimmed))
|
||||
{
|
||||
return await CurrencyConverter.ConvertAsync(trimmed, ct);
|
||||
}
|
||||
|
||||
// ─── 단위 변환 우선 감지 ──────────────────────────────────────────────
|
||||
// ─── 단위 변환 (명시적 목표: 100km in miles) ─────────────────────────
|
||||
if (UnitConverter.TryConvert(trimmed, out var convertResult))
|
||||
{
|
||||
return
|
||||
@@ -58,6 +62,11 @@ public class CalculatorHandler : IActionHandler
|
||||
];
|
||||
}
|
||||
|
||||
// ─── 단위 단축 자동 제안 (20km, 100f, 5lb …) ──────────────────────────
|
||||
var autoSuggest = UnitConverter.AutoSuggest(trimmed);
|
||||
if (autoSuggest.Count > 0)
|
||||
return autoSuggest;
|
||||
|
||||
// ─── 수식 계산 ────────────────────────────────────────────────────────
|
||||
try
|
||||
{
|
||||
|
||||
@@ -151,4 +151,131 @@ internal static class UnitConverter
|
||||
return ((long)v).ToString("N0", System.Globalization.CultureInfo.CurrentCulture);
|
||||
return v.ToString("G6", System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
// ─── 자동 제안 (목표 단위 없이 입력 시) ──────────────────────────────────
|
||||
|
||||
// 패턴: <숫자><단위> (공백 없어도 OK)
|
||||
private static readonly Regex _autoPattern = new(
|
||||
@"^(-?\d+(?:\.\d+)?)\s*([a-z°/²³µ]+)$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
// 단위 → 주요 변환 대상 목록 (사용자에게 자동 제안)
|
||||
private static readonly Dictionary<string, string[]> _suggestions = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
// 길이
|
||||
["km"] = ["miles", "m", "ft"],
|
||||
["miles"] = ["km", "m", "ft"],
|
||||
["m"] = ["km", "ft", "inches"],
|
||||
["cm"] = ["inches", "m"],
|
||||
["mm"] = ["inches", "cm"],
|
||||
["ft"] = ["m", "km", "inches"],
|
||||
["feet"] = ["m", "km", "inches"],
|
||||
["in"] = ["cm", "m"],
|
||||
["inches"]= ["cm", "m"],
|
||||
// 무게
|
||||
["kg"] = ["lb", "g"],
|
||||
["lb"] = ["kg", "g"],
|
||||
["lbs"] = ["kg", "g"],
|
||||
["g"] = ["kg", "oz"],
|
||||
["oz"] = ["g", "kg"],
|
||||
// 속도
|
||||
["km/h"] = ["mph", "m/s"],
|
||||
["kmh"] = ["mph", "m/s"],
|
||||
["kph"] = ["mph", "m/s"],
|
||||
["mph"] = ["km/h", "m/s"],
|
||||
["m/s"] = ["km/h", "mph"],
|
||||
// 데이터
|
||||
["mb"] = ["gb", "kb"],
|
||||
["gb"] = ["mb", "tb"],
|
||||
["tb"] = ["gb", "mb"],
|
||||
["kb"] = ["mb", "b"],
|
||||
// 온도 — 양방향
|
||||
["c"] = ["f", "k"],
|
||||
["°c"] = ["°f", "k"],
|
||||
["celsius"]= ["fahrenheit", "kelvin"],
|
||||
["f"] = ["c", "k"],
|
||||
["°f"] = ["°c", "k"],
|
||||
["fahrenheit"] = ["celsius", "kelvin"],
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// "20km", "100f" 처럼 목표 단위 없이 입력하면 주요 변환 대상을 자동 제안합니다.
|
||||
/// </summary>
|
||||
public static List<LauncherItem> AutoSuggest(string input)
|
||||
{
|
||||
var m = _autoPattern.Match(input.Trim());
|
||||
if (!m.Success) return new();
|
||||
|
||||
if (!double.TryParse(m.Groups[1].Value,
|
||||
System.Globalization.NumberStyles.Float,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out var value))
|
||||
return new();
|
||||
|
||||
var fromUnit = m.Groups[2].Value;
|
||||
if (!_suggestions.TryGetValue(fromUnit, out var targets)) return new();
|
||||
|
||||
var items = new List<LauncherItem>();
|
||||
foreach (var to in targets)
|
||||
{
|
||||
if (TryConvert($"{value} {fromUnit} in {to}", out var r) && r != null)
|
||||
{
|
||||
items.Add(new LauncherItem(
|
||||
r,
|
||||
$"{value} {fromUnit} → {to} · Enter로 복사",
|
||||
null, r, Symbol: Symbols.Calculator));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── = 접두어에서 today 날짜 단축 계산 ────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// "today+30d", "today-7d", "today+2w" 등 날짜 단축 패턴을 = 핸들러에서 처리합니다.
|
||||
/// DateCalcHandler의 로직을 재사용합니다.
|
||||
/// </summary>
|
||||
internal static class DateShortcut
|
||||
{
|
||||
private static readonly Regex _pattern = new(
|
||||
@"^today\s*([+-])(\d+)([dDwWmMyY])$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public static bool TryMatch(string input, out IEnumerable<LauncherItem>? items)
|
||||
{
|
||||
items = null;
|
||||
var m = _pattern.Match(input.Trim());
|
||||
if (!m.Success) return false;
|
||||
|
||||
var sign = m.Groups[1].Value == "+" ? 1 : -1;
|
||||
var val = int.Parse(m.Groups[2].Value) * sign;
|
||||
var unit = m.Groups[3].Value.ToLowerInvariant();
|
||||
var now = DateTime.Now;
|
||||
|
||||
var target = unit switch
|
||||
{
|
||||
"d" => now.AddDays(val),
|
||||
"w" => now.AddDays(val * 7),
|
||||
"m" => now.AddMonths(val),
|
||||
"y" => now.AddYears(val),
|
||||
_ => now.AddDays(val),
|
||||
};
|
||||
|
||||
var diff = (target.Date - now.Date).Days;
|
||||
var dayName = target.ToString("dddd", new System.Globalization.CultureInfo("ko-KR"));
|
||||
var dateStr = target.ToString("yyyy-MM-dd");
|
||||
var diffText = diff == 0 ? "오늘"
|
||||
: diff > 0 ? $"+{diff}일 후"
|
||||
: $"{-diff}일 전";
|
||||
|
||||
items = new List<LauncherItem>
|
||||
{
|
||||
new LauncherItem(
|
||||
$"{dateStr} ({dayName})",
|
||||
$"{input} → {diffText} · Enter로 복사",
|
||||
null, dateStr, Symbol: Symbols.Calculator),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user