[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:
2026-04-04 11:15:11 +09:00
parent fc881124b9
commit 75cb4ba6e9
6 changed files with 330 additions and 1 deletions

View File

@@ -29,6 +29,10 @@ public partial class LauncherViewModel : INotifyPropertyChanged
private const int DebounceMs = 30; // 30ms 디바운스 — 연속 입력 시 중간 검색 스킵
private string _lastSearchQuery = ""; // IME 조합 완성 후 동일 쿼리 재검색 방지용
// ─── 검색 히스토리 탐색 ──────────────────────────────────────────────────
private bool _isHistoryNavigation; // InputText setter에서 히스토리 인덱스 리셋 방지
private int _historyIndex = -1; // -1 = 탐색 중 아님
// ─── 파일 액션 모드 ───────────────────────────────────────────────────────
private bool _isActionMode;
private LauncherItem? _actionSourceItem;
@@ -66,6 +70,8 @@ public partial class LauncherViewModel : INotifyPropertyChanged
{
if (_inputText == value) return;
_inputText = value;
// 사용자 직접 입력 시 히스토리 탐색 위치 초기화
if (!_isHistoryNavigation) _historyIndex = -1;
OnPropertyChanged();
OnPropertyChanged(nameof(HasActivePrefix));
OnPropertyChanged(nameof(ActivePrefixLabel));
@@ -276,10 +282,49 @@ public partial class LauncherViewModel : INotifyPropertyChanged
if (IsActionMode) { IsActionMode = false; _actionSourceItem = null; }
Results.Clear();
_lastSearchQuery = "";
_historyIndex = -1;
ClearMerge();
LoadQuickActions();
}
// ─── 검색 히스토리 탐색 ──────────────────────────────────────────────────
/// <summary>
/// ↑ 키 — 이전(오래된) 히스토리 항목으로 이동.
/// null이면 히스토리 없음.
/// </summary>
public string? NavigateHistoryPrev()
{
var history = SearchHistoryService.GetAll();
if (history.Count == 0) return null;
_historyIndex = Math.Min(_historyIndex + 1, history.Count - 1);
return history[_historyIndex];
}
/// <summary>
/// ↓ 키 — 최신 히스토리 항목으로 이동.
/// _historyIndex가 0 이하면 빈 문자열(현재 입력으로 복귀).
/// </summary>
public string? NavigateHistoryNext()
{
if (_historyIndex <= 0) { _historyIndex = -1; return ""; }
_historyIndex--;
var history = SearchHistoryService.GetAll();
return _historyIndex >= 0 && _historyIndex < history.Count
? history[_historyIndex] : "";
}
/// <summary>
/// 히스토리에서 탐색한 텍스트를 입력창에 설정합니다.
/// InputText setter의 히스토리 인덱스 리셋을 억제합니다.
/// </summary>
public void SetInputFromHistory(string text)
{
_isHistoryNavigation = true;
try { InputText = text; }
finally { _isHistoryNavigation = false; }
}
/// <summary>
/// UsageRankingService 상위 항목에서 퀵 액션 칩을 생성합니다.
/// 실제로 존재하는 파일/폴더만 표시하며 최대 8개로 제한합니다.
@@ -405,6 +450,10 @@ public partial class LauncherViewModel : INotifyPropertyChanged
if (SelectedItem == null) return;
// 실행 전 검색어 히스토리 저장 (2자 이상, prefix 포함)
if (InputText.Trim().Length >= 2)
SearchHistoryService.Add(InputText.Trim());
// 창을 먼저 닫아 체감 속도 확보 → 실행은 백그라운드
CloseRequested?.Invoke(this, EventArgs.Empty);