P02: Application Launcher with Fuzzy Search

P02: Application Launcher with Fuzzy Search

Project Overview

What you’ll build: A Spotlight/Alfred-like launcher for Windows–press a hotkey, type a few characters, and launch any program, file, or folder using fuzzy matching.

Attribute Value
Difficulty Level 2: Intermediate
Time Estimate 1-2 weeks
Programming Language AutoHotkey v2
Knowledge Area Windows Automation, Search Algorithms, GUI Design
Prerequisites Basic AutoHotkey syntax, understanding of filesystem concepts

1. Learning Objectives

After completing this project, you will be able to:

  1. Traverse the Windows filesystem - Recursively scan directories using both iterative and recursive approaches, understanding the tradeoffs
  2. Implement string matching algorithms - Build fuzzy search using Levenshtein distance, subsequence matching, and scoring heuristics
  3. Design for performance - Create indexes that enable sub-millisecond search on thousands of items
  4. Build responsive GUIs - Keep interfaces snappy by separating indexing from searching
  5. Work with Windows special folders - Navigate Program Files, Start Menu, AppData, and understand Windows application deployment
  6. Implement result ranking - Combine match quality with usage frequency for intelligent ordering
  7. Track usage patterns - Persist user behavior to improve future predictions
  8. Handle Windows shortcuts - Resolve .lnk files using COM automation
  9. Apply debouncing patterns - Prevent excessive computation during rapid user input

2. Deep Theoretical Foundation

2.1 Filesystem Traversal and Indexing Strategies

The Windows filesystem is a hierarchical tree structure where directories (folders) contain files and other directories. When building an application launcher, you need to visit every node in this tree to find launchable items.

                    C:\
                     |
         +-----------+-----------+
         |           |           |
    Program Files  Users     Windows
         |           |           |
     +---+---+    Douglas    System32
     |       |       |           |
   Chrome  Python  AppData    (skip)
     |       |       |
  chrome.exe python.exe  Local
                          |
                      Programs
                          |
                      +---+---+
                      |       |
                   Discord  VSCode
                      |       |
                  discord.exe code.exe

Two Fundamental Traversal Approaches:

1. Recursive Traversal (Depth-First)

Algorithm: RecursiveTraverse(folder)
    for each item in folder:
        if item is file AND matches criteria:
            add to index
        else if item is directory:
            RecursiveTraverse(item)    <-- Stack frame created

Visual of stack frames during recursion:

Call Stack:
|-------------------------|
| RecursiveTraverse(Chrome) | <- Current call
|-------------------------|
| RecursiveTraverse(Program Files) |
|-------------------------|
| RecursiveTraverse(C:\)  |
|-------------------------|

As we go deeper, stack grows. Deep nesting = stack overflow risk!

Pros: Elegant, easy to understand, natural for tree structures Cons: Stack overflow risk for deep paths (Windows allows 32,767 characters in paths)

2. Iterative Traversal (Breadth-First using Queue)

Algorithm: IterativeTraverse(rootFolder)
    queue = [rootFolder]
    while queue is not empty:
        folder = queue.dequeue()
        for each item in folder:
            if item is file AND matches criteria:
                add to index
            else if item is directory:
                queue.enqueue(item)    <-- Uses heap memory

Visual of queue during traversal:

Step 1: queue = [C:\]
        Processing C:\, found: Program Files, Users, Windows
        queue = [Program Files, Users, Windows]

Step 2: queue = [Users, Windows]
        Processing Program Files, found: Chrome, Python
        queue = [Users, Windows, Chrome, Python]

Step 3: queue = [Windows, Chrome, Python]
        Processing Users, found: Douglas
        queue = [Windows, Chrome, Python, Douglas]

... continues until queue is empty

Pros: No stack overflow, uses heap memory, can handle any depth Cons: Slightly more code, less intuitive

AutoHotkey’s Built-in Solution:

; The 'R' flag enables recursive traversal
Loop Files, "C:\Program Files\*.exe", "R"
{
    ; A_LoopFileFullPath contains the complete path
    ; A_LoopFileName contains just the filename
}

AutoHotkey handles the recursion internally, but understanding the underlying concepts helps you debug performance issues.

Indexing Strategy: Scan Once, Search Many Times

                    Script Startup
                          |
                          v
              +------------------------+
              |   Scan All Folders     |  <-- Takes 2-5 seconds
              |   (One-time cost)      |
              +------------------------+
                          |
                          v
              +------------------------+
              |   Build In-Memory      |
              |   Index Array          |
              +------------------------+
                          |
                          v
         +----------------+----------------+
         |                |                |
    Search #1         Search #2       Search #N
    (< 5ms)           (< 5ms)         (< 5ms)

    User types "chr"   User types "pyt"   etc.

What to Index (Program Locations on Windows):

Location Contains Priority
%APPDATA%\Microsoft\Windows\Start Menu\Programs User shortcuts (.lnk) High
C:\ProgramData\Microsoft\Windows\Start Menu\Programs System-wide shortcuts High
C:\Program Files 64-bit applications Medium
C:\Program Files (x86) 32-bit applications Medium
%LOCALAPPDATA%\Programs User-installed apps (Discord, VS Code) Medium
%USERPROFILE%\Desktop Desktop shortcuts Medium
Registry App Paths Registered executables Low

2.2 String Matching and Fuzzy Search Algorithms

String matching is the core intellectual challenge of this project. Users expect “intelligent” behavior–the launcher should understand intent, not just literal text.

The Spectrum of String Matching:

Strictness                                           Flexibility
    |                                                     |
    v                                                     v
+----------+     +--------+     +----------+     +-------+
|  Exact   | --> | Prefix | --> | Substring| --> | Fuzzy |
|  Match   |     | Match  |     |  Match   |     | Match |
+----------+     +--------+     +----------+     +-------+
  "chrome"       "chr..."       "...rom..."      "chrme"
  = "chrome"     matches        matches          matches
                 "chrome"       "chrome"         "chrome"

1. Exact Matching

Query:  "chrome"
Target: "chrome"
Result: MATCH (query == target)

Query:  "Chrome"
Target: "chrome"
Result: NO MATCH (case-sensitive) or MATCH (case-insensitive)

2. Prefix Matching

Query:  "chr"
Target: "chrome"

Check: Does target start with query?
       "chrome".startsWith("chr") = true

       c h r o m e
       | | |
       c h r  <- Query matches beginning

Result: MATCH

3. Substring/Contains Matching

Query:  "rom"
Target: "chrome"

Check: Does target contain query anywhere?
       "chrome".contains("rom") = true

       c h r o m e
           | | |
           r o m  <- Query found inside

Result: MATCH (but weaker than prefix)

4. Fuzzy Matching - Subsequence Algorithm

The key insight: users type characters in order, but may skip some.

Query:  "vscd"
Target: "Visual Studio Code"

Attempt to find all query characters in order:

V i s u a l   S t u d i o   C o d e
^             ^             ^   ^
v             s             c   d

Query characters found in sequence: v-s-c-d
All 4 characters found in order!

Result: MATCH

Subsequence Matching Algorithm (Pseudocode):

function subsequenceMatch(query, target):
    queryIndex = 0
    targetIndex = 0

    while queryIndex < query.length AND targetIndex < target.length:
        if query[queryIndex] == target[targetIndex]:
            queryIndex++    // Found this character, move to next query char
        targetIndex++       // Always move through target

    // If we matched all query characters, it's a match
    return queryIndex == query.length

Visual trace of algorithm:

Query:  "pyt"
Target: "PyTest"

Step 1: query[0]='p' vs target[0]='P' -> MATCH (case-insensitive)
        queryIndex=1, targetIndex=1

Step 2: query[1]='y' vs target[1]='y' -> MATCH
        queryIndex=2, targetIndex=2

Step 3: query[2]='t' vs target[2]='T' -> MATCH
        queryIndex=3, targetIndex=3

Result: queryIndex(3) == query.length(3) -> TRUE, it's a match!

5. Levenshtein Distance (Edit Distance)

For true typo tolerance, we calculate the minimum edits needed to transform one string into another.

Operations allowed:

  • Insert a character
  • Delete a character
  • Replace a character
Query:  "chrme"    (user typo)
Target: "chrome"

Alignment:
c h r m e
c h r o m e

Edits needed:
1. Insert 'o' after 'r'

Distance = 1 (very close match!)

Levenshtein Distance Algorithm (Dynamic Programming):

function levenshteinDistance(s1, s2):
    // Create matrix of size (len(s1)+1) x (len(s2)+1)

    For "cat" vs "car":

        ""  c  a  t
    ""   0  1  2  3
    c    1  0  1  2
    a    2  1  0  1
    r    3  2  1  1  <- Final answer: 1 edit

    Each cell [i,j] = minimum of:
        - cell[i-1,j] + 1      (delete)
        - cell[i,j-1] + 1      (insert)
        - cell[i-1,j-1] + (0 if match else 1)  (replace or no-op)

Practical Consideration: Levenshtein is O(n*m) complexity. For a launcher with 5000 items, this can be slow. Use it only as a fallback after faster algorithms fail.

6. Scoring System Design

Not all matches are equal. We need a scoring system:

Score Components:
+------------------------------------------+
| Match Type          | Base Score         |
+------------------------------------------+
| Exact match         | 1000               |
| Prefix match        | 500 + ratio bonus  |
| Substring match     | 200 + ratio bonus  |
| Subsequence match   | 100 + bonuses      |
| Levenshtein <= 2    | 50                 |
| No match            | 0                  |
+------------------------------------------+

Ratio bonus = (query.length / target.length) * 100
Consecutive bonus = +10 per consecutive matched character
Start-of-word bonus = +30 if match starts at word boundary

Example Scoring:

Query: "vs"

Target 1: "VS Code"
- Prefix match: 500
- Ratio: 2/7 * 100 = 28
- Starts at word: +30
- Score: 558

Target 2: "Visual Studio"
- Subsequence match: 100
- V and S are at word starts: +30 each
- Score: 160

Result: "VS Code" ranks higher (correct!)

2.3 Caching and Performance Optimization

Performance is critical for a launcher–users expect instant results.

The Caching Hierarchy:

Speed        +-------------------+
  ^          |    CPU Cache      |   < 1 nanosecond
  |          +-------------------+
  |          |       RAM         |   ~100 nanoseconds
  |          +-------------------+
  |          |   SSD/NVMe        |   ~100 microseconds
  |          +-------------------+
  |          |       HDD         |   ~10 milliseconds
  v          +-------------------+
Slow         |     Network       |   ~100 milliseconds

Strategy: Keep the index in RAM

Startup:
1. Read from disk (slow, once)
2. Build array in RAM
3. All searches use RAM (fast, many times)

                    +-----------------+
   Disk (HDD/SSD)   |  index.json     |
                    +-----------------+
                           |
                           | Load once at startup
                           v
                    +-----------------+
   RAM              |  AppIndex[]     |  <- All searches here
                    |  5000 items     |
                    |  ~2MB memory    |
                    +-----------------+

Optimization Techniques:

1. Prefix Hashtable for O(1) Lookup

Instead of searching all 5000 items for "chr":

Standard array: Check all 5000 items = O(n)

Prefix hashtable:
    prefixMap = {
        "a":   [items starting with a...],
        "b":   [items starting with b...],
        ...
        "chr": [Chrome, ChromeDriver],  <- Direct lookup!
        ...
    }

    Search "chr": prefixMap["chr"] = [Chrome, ChromeDriver]
    Only 2 items to check!

2. Early Termination

function search(query, index, maxResults=10):
    results = []
    for item in index:
        if results.length >= maxResults * 3:  // Got enough candidates
            break                               // Stop searching
        score = calculateScore(query, item)
        if score > 0:
            results.add({item, score})
    return results.sortByScore().take(maxResults)

3. Lazy Scoring

// Bad: Calculate full Levenshtein for every item
for item in index:
    score = levenshteinDistance(query, item.name)  // Expensive!

// Good: Quick filter first, expensive scoring only for survivors
for item in index:
    if quickMatch(query, item):        // Fast check
        score = fullScore(query, item) // Only for candidates

2.4 GUI Responsiveness and Event Loops

Understanding event loops is essential for building responsive applications.

The Windows Message Loop:

+------------------+
| Windows OS       |
+------------------+
        |
        | WM_KEYDOWN, WM_PAINT, WM_TIMER, etc.
        v
+------------------+
| Message Queue    |  <- Messages wait here
| [msg1, msg2, ...]|
+------------------+
        |
        v
+------------------+
| GetMessage()     |  <- AutoHotkey's event loop
| DispatchMessage()|
+------------------+
        |
        v
+------------------+
| Your Handler     |
| OnTextChange()   |
+------------------+

The Problem: Blocking the Main Thread

Timeline:
0ms     User types 'p'
        |
        +-> OnTextChange fires
            |
            +-> Search 5000 items (500ms!)
                |
                +-> GUI frozen!
                    |
                    User types 'y' 't' 'h'
                    |
                    +-> These events queue up
                        (UI unresponsive)
                        |
500ms                   Search completes
                        |
                        +-> Process queued 'y' 't' 'h'
                            (feels laggy)

The Solution: Fast Operations + Debouncing

Timeline with debouncing:

0ms     User types 'p'
        +-> OnTextChange: Start 100ms timer

50ms    User types 'y'
        +-> OnTextChange: Cancel old timer, start new 100ms timer

100ms   User types 't'
        +-> OnTextChange: Cancel old timer, start new 100ms timer

200ms   Timer fires! User stopped typing.
        +-> Search for "pyt" (fast, <10ms because index is in RAM)
        +-> Update GUI

Total: Smooth experience, only 1 search performed

Debouncing Implementation Pattern:

+--------+     +--------+     +--------+
| Type p | --> | Type y | --> | Type t | --> [100ms pause] --> Search!
+--------+     +--------+     +--------+
     |              |              |
   Start          Reset          Reset
   Timer          Timer          Timer
; Pseudocode for debouncing
global searchTimer := 0

OnTextChange(ctrl) {
    global searchTimer

    ; Cancel any pending search
    if (searchTimer)
        SetTimer(searchTimer, 0)

    ; Schedule new search in 100ms
    searchTimer := SetTimer(DoSearch, -100)
}

DoSearch() {
    ; This only runs after 100ms of no typing
    results := FuzzySearch(currentQuery, appIndex)
    UpdateResultsList(results)
}

2.5 Windows Special Folders and Program Locations

Windows has a complex system for where programs are installed.

The Windows Program Landscape:

+-----------------------------------------------------------+
|                    Windows Filesystem                       |
+-----------------------------------------------------------+
|                                                             |
|  C:\Program Files\                                          |
|  +-- Microsoft Office\                                      |
|  |   +-- Office16\                                          |
|  |       +-- WINWORD.EXE     <- 64-bit Microsoft Word      |
|  +-- Google\                                                |
|  |   +-- Chrome\                                            |
|  |       +-- Application\                                   |
|  |           +-- chrome.exe  <- 64-bit Chrome              |
|  +-- Python311\                                             |
|      +-- python.exe          <- 64-bit Python              |
|                                                             |
|  C:\Program Files (x86)\                                    |
|  +-- Steam\                                                 |
|      +-- steam.exe           <- 32-bit Steam               |
|                                                             |
|  C:\Users\Douglas\                                          |
|  +-- AppData\                                               |
|      +-- Local\                                             |
|      |   +-- Programs\                                      |
|      |       +-- Microsoft VS Code\                         |
|      |           +-- Code.exe <- Per-user install          |
|      +-- Roaming\                                           |
|          +-- Microsoft\                                     |
|              +-- Windows\                                   |
|                  +-- Start Menu\                            |
|                      +-- Programs\                          |
|                          +-- Discord.lnk  <- Shortcut!     |
|                          +-- VS Code.lnk                    |
|                                                             |
|  C:\ProgramData\Microsoft\Windows\Start Menu\Programs\      |
|  +-- Word.lnk                <- System-wide shortcut       |
|  +-- Excel.lnk                                              |
|                                                             |
+-----------------------------------------------------------+

Understanding .lnk Shortcuts:

A shortcut file doesn’t contain the program–it points to it.

File: C:\Users\Douglas\Desktop\Chrome.lnk
      |
      +-- Target: C:\Program Files\Google\Chrome\Application\chrome.exe
      +-- Arguments: --profile-directory="Default"
      +-- Working Dir: C:\Program Files\Google\Chrome\Application
      +-- Icon: C:\Program Files\Google\Chrome\Application\chrome.exe,0

Resolving Shortcuts with COM:

ResolveShortcut(lnkPath) {
    ; Create Windows Script Host Shell object
    shell := ComObject("WScript.Shell")

    ; Create shortcut object
    shortcut := shell.CreateShortcut(lnkPath)

    ; Extract properties
    return {
        path: shortcut.TargetPath,        ; The actual .exe
        args: shortcut.Arguments,          ; Command-line arguments
        workingDir: shortcut.WorkingDirectory,
        icon: shortcut.IconLocation
    }
}

Environment Variables for Special Folders:

Variable Typical Path Use
%APPDATA% C:\Users\Douglas\AppData\Roaming User settings
%LOCALAPPDATA% C:\Users\Douglas\AppData\Local User-installed apps
%PROGRAMFILES% C:\Program Files 64-bit apps
%PROGRAMFILES(X86)% C:\Program Files (x86) 32-bit apps
%PROGRAMDATA% C:\ProgramData System-wide app data
%USERPROFILE% C:\Users\Douglas User home

In AutoHotkey:

; Use A_AppData, A_ProgramFiles, etc.
startMenuPath := A_AppData . "\Microsoft\Windows\Start Menu\Programs"

3. Complete Project Specification

Functional Requirements

ID Requirement Priority Notes
F1 Scan standard application directories for executables Must Have Program Files, Start Menu
F2 Build an in-memory index on startup Must Have With loading indicator
F3 Display search popup on configurable hotkey Must Have Default: Alt+Space
F4 Filter index as user types with <50ms response Must Have Real-time filtering
F5 Implement fuzzy matching for typo tolerance Must Have Subsequence + scoring
F6 Launch selected application Must Have Handle .exe and .lnk
F7 Support .exe executable files Must Have Direct launch
F8 Support .lnk shortcut files Must Have Resolve target first
F9 Rank results by match quality Must Have Exact > Prefix > Fuzzy
F10 Display application icons Should Have Extract from .exe
F11 Track frequently used applications Should Have Persist launch counts
F12 Rank results by usage frequency Should Have Combine with match score
F13 Re-scan index on demand Should Have Hotkey or menu option
F14 Support launching with arguments Nice to Have For shortcuts with args
F15 Custom search paths in config Nice to Have User-configurable
F16 Keyboard navigation (Up/Down arrows) Must Have Navigate in search box
F17 Close on Escape or focus loss Must Have Standard UX pattern

Non-Functional Requirements

ID Requirement Threshold
NF1 Startup indexing time < 5 seconds
NF2 Search response time < 50ms after typing stops
NF3 GUI appearance time < 100ms from hotkey press
NF4 Memory usage < 100MB RAM
NF5 Compatibility Windows 10/11 without admin rights
NF6 Index size Handle 10,000+ items
NF7 Debounce delay 80-150ms configurable

Input/Output Specification

Inputs:

  • Hotkey press (Alt+Space by default)
  • Search text from user
  • Arrow key navigation
  • Enter key to launch
  • Escape key to close

Outputs:

  • GUI popup with search results
  • Launched application
  • Persisted usage statistics

4. Real World Outcome

When complete, you’ll have a launcher you use dozens of times daily:

Example GUI Mockup (ASCII)

Initial State (No Query):

+--------------------------------------------------+
|  [Search box with focus]                          |
|  ________________________________________________ |
| |                                                | |
| |________________________________________________| |
|                                                   |
|  RECENT                                           |
|  +----------------------------------------------+ |
|  | > [icon] Visual Studio Code          (1m)   | |
|  |   [icon] Google Chrome               (5m)   | |
|  |   [icon] Windows Terminal           (12m)   | |
|  |   [icon] Slack                      (1h)    | |
|  |   [icon] Discord                    (2h)    | |
|  +----------------------------------------------+ |
|                                                   |
|  Press Enter to launch | Esc to close             |
+--------------------------------------------------+

Active Search:

+--------------------------------------------------+
|  [pyt]                                           |
|  ________________________________________________ |
| | pyt                                            | |
| |________________________________________________| |
|                                                   |
|  RESULTS                                3 matches |
|  +----------------------------------------------+ |
|  | > [py] Python 3.11                   *****  | |
|  |        C:\Python311\python.exe               | |
|  |   [py] PyCharm Professional          ****   | |
|  |        C:\Program Files\JetBrains\...        | |
|  |   [py] pytest                        ***    | |
|  |        C:\Users\...\venv\Scripts\pytest.exe  | |
|  +----------------------------------------------+ |
|                                                   |
|  Arrow keys to navigate | Enter to launch         |
+--------------------------------------------------+

Fuzzy Match Example:

+--------------------------------------------------+
|  [vscd]                                          |
|  ________________________________________________ |
| | vscd                                           | |
| |________________________________________________| |
|                                                   |
|  RESULTS                                1 match   |
|  +----------------------------------------------+ |
|  | > [vs] Visual Studio Code            ****   | |
|  |        Matched: V-isual S-tudio C-o-D-e      | |
|  |        C:\Users\...\Programs\VS Code\Code.exe | |
|  +----------------------------------------------+ |
|                                                   |
|  Fuzzy match: vscd in "Visual Studio Code"        |
+--------------------------------------------------+

Example Search Interactions

Scenario 1: Quick Launch of Frequent App

User Action                    Launcher Response
-----------                    -----------------
Press Alt+Space                GUI appears instantly (<100ms)
Type "c"                       Shows: Chrome, Calculator, CMD, Code...
                               (Chrome first because launched 47 times)
Press Enter                    Chrome launches, GUI closes

Total time: ~500ms

Scenario 2: Finding Unfamiliar App

User Action                    Launcher Response
-----------                    -----------------
Press Alt+Space                GUI appears with recent apps
Type "disk"                    Shows: Disk Cleanup, Disk Management
Arrow Down                     Selects "Disk Management"
Press Enter                    Disk Management launches

Total time: ~2 seconds

Scenario 3: Fuzzy Match with Typo

User Action                    Launcher Response
-----------                    -----------------
Press Alt+Space                GUI appears
Type "chrmoe" (typo!)          Shows: Chrome (fuzzy matched)
                               Score breakdown: subsequence + low edit distance
Press Enter                    Chrome launches

Total time: ~800ms

Performance Expectations

Operation Target Measurement Method
Index build (1000 items) < 2 seconds StartTime to LoadComplete
Index build (5000 items) < 5 seconds StartTime to LoadComplete
Search (empty to results) < 30ms KeyUp to ListUpdate
GUI show < 100ms HotkeyPress to WindowVisible
Launch application < 200ms EnterPress to ProcessStart

Frequency Tracking Behavior

Launch History Tracking:
+--------------------------------------------------+
| App Name          | Launch Count | Last Used     |
+--------------------------------------------------+
| Chrome            | 127          | 2 min ago     |
| VS Code           | 89           | 15 min ago    |
| Terminal          | 64           | 1 hour ago    |
| Discord           | 23           | 3 hours ago   |
+--------------------------------------------------+

Ranking Formula:
  FinalScore = MatchScore + (UsageCount * UsageWeight)

  where UsageWeight = 5 (configurable)

Example for query "c":
  Chrome:  PrefixMatch(100) + (127 * 5) = 735
  Code:    PrefixMatch(100) + (89 * 5)  = 545
  CMD:     PrefixMatch(100) + (12 * 5)  = 160

  Result order: Chrome, VS Code, CMD

5. Solution Architecture

Component Diagram (ASCII)

+========================================================================+
||                     APPLICATION LAUNCHER                              ||
+========================================================================+
|                                                                         |
|   +------------------+                      +----------------------+    |
|   |   CONFIG FILE    |                      |    USAGE DATA FILE   |    |
|   |   config.ini     |<-------------------->|    usage.json        |    |
|   +------------------+     Load/Save        +----------------------+    |
|          |                                             ^                |
|          v                                             |                |
|   +------------------+                                 |                |
|   |     Settings     |                                 |                |
|   | - hotkey         |                                 |                |
|   | - maxResults     |                                 |                |
|   | - debounceMs     |                                 |                |
|   | - searchPaths[]  |                                 |                |
|   +------------------+                                 |                |
|          |                                             |                |
|          v                                             |                |
|   +------------------------------------------------------------------+  |
|   |                         INDEXER                                   | |
|   |   +------------------+     +------------------+                   | |
|   |   | FilesystemScanner|     | ShortcutResolver |                   | |
|   |   | - ScanDirectory()|     | - ResolveLink()  |                   | |
|   |   +------------------+     +------------------+                   | |
|   |            |                        |                             | |
|   |            v                        v                             | |
|   |   +------------------------------------------------+              | |
|   |   |              APP INDEX (Array)                 |              | |
|   |   | [AppItem, AppItem, AppItem, ...]               |              | |
|   |   +------------------------------------------------+              | |
|   +------------------------------------------------------------------+  |
|                                      |                                  |
|                                      v                                  |
|   +------------------------------------------------------------------+  |
|   |                       SEARCH ENGINE                              |  |
|   |   +------------------+     +------------------+                  |  |
|   |   |  FuzzyMatcher    |     |  ResultRanker    |                  |  |
|   |   | - ExactMatch()   |     | - CombineScores()|                  |  |
|   |   | - PrefixMatch()  |     | - SortResults()  |                  |  |
|   |   | - Subsequence()  |     | - ApplyUsage()   |                  |  |
|   |   | - EditDistance() |     +------------------+                  |  |
|   |   +------------------+                                           |  |
|   +------------------------------------------------------------------+  |
|                                      |                                  |
|          +---------------------------+---------------------------+      |
|          v                                                       v      |
|   +------------------+                                +------------------+
|   |   HOTKEY HANDLER |                                |  GUI MANAGER    |
|   | - RegisterHotkey |                                | - CreateWindow  |
|   | - ToggleGUI()    |                                | - ShowResults   |
|   +------------------+                                | - HandleInput   |
|          |                                            +------------------+
|          v                                                       |      |
|   +------------------------------------------------------------------+  |
|   |                        LAUNCHER                                  |  |
|   |   - Run(path, args)                                              |  |
|   |   - UpdateUsage(item)                                            |  |
|   |   - SaveUsageData()                                              |  |
|   +------------------------------------------------------------------+  |
|                                                                         |
+=========================================================================+

Index Data Structure Design

; Individual application item
AppItem := {
    ; Core identification
    name: "Google Chrome",                    ; Display name
    path: "C:\Program Files\Google\...\chrome.exe",  ; Launch path

    ; Launch configuration
    args: "--new-window",                     ; Optional arguments
    workingDir: "C:\Program Files\...",       ; Working directory

    ; Search optimization
    lowerName: "google chrome",               ; Pre-computed lowercase
    keywords: ["chrome", "browser", "web", "google"],  ; Additional search terms

    ; Metadata
    isShortcut: false,                        ; Was this from a .lnk file?
    originalPath: "",                         ; If shortcut, the .lnk path

    ; Usage tracking
    usageCount: 47,                           ; Times launched
    lastUsed: "20251227120000",               ; A_Now format timestamp

    ; Display
    iconPath: "C:\Program Files\...\chrome.exe",  ; For icon extraction
    iconIndex: 0                              ; Icon index in the file
}

; Full index structure
AppIndex := [AppItem1, AppItem2, AppItem3, ...]

; Usage data (persisted separately)
UsageData := Map(
    "C:\...\chrome.exe" => {count: 47, lastUsed: "20251227120000"},
    "C:\...\code.exe" => {count: 89, lastUsed: "20251227110000"},
    ...
)

Memory Layout Visualization:

AppIndex Array in RAM:
+------------------------------------------------------------------+
| Index 0    | Index 1    | Index 2    |    ...    | Index 4999   |
+------------------------------------------------------------------+
|  AppItem   |  AppItem   |  AppItem   |    ...    |   AppItem    |
| (Chrome)   | (Firefox)  | (VS Code)  |    ...    |   (Zoom)     |
+------------------------------------------------------------------+
     |
     v
+---------------------------+
| name: "Google Chrome"     |
| path: "C:\...\chrome.exe" |
| lowerName: "google chrome"|
| keywords: [...]           |
| usageCount: 47            |
| ...                       |
+---------------------------+

Estimated memory per item: ~500 bytes
5000 items = ~2.5 MB RAM

Search Algorithm Design

+--------------------------------------------------------------------+
|                    SEARCH ALGORITHM PIPELINE                        |
+--------------------------------------------------------------------+
|                                                                     |
|   INPUT: query = "chro"                                             |
|                                                                     |
|   Step 1: Normalize Query                                           |
|   +-------------------+                                             |
|   | query.lower()     |  "chro" -> "chro"                          |
|   | query.trim()      |                                             |
|   +-------------------+                                             |
|             |                                                       |
|             v                                                       |
|   Step 2: Quick Filter (Prefix Check)                               |
|   +-----------------------------------------------------------+    |
|   | For each item in AppIndex:                                 |    |
|   |   if item.lowerName.startsWith(query) OR                   |    |
|   |      any(keyword.startsWith(query) for keyword in item):   |    |
|   |        add to candidates with HIGH priority                |    |
|   +-----------------------------------------------------------+    |
|             |                                                       |
|             v                                                       |
|   Step 3: Substring Check                                           |
|   +-----------------------------------------------------------+    |
|   | For remaining items:                                       |    |
|   |   if item.lowerName.contains(query):                       |    |
|   |        add to candidates with MEDIUM priority              |    |
|   +-----------------------------------------------------------+    |
|             |                                                       |
|             v                                                       |
|   Step 4: Fuzzy Match (if few results)                              |
|   +-----------------------------------------------------------+    |
|   | For remaining items:                                       |    |
|   |   if subsequenceMatch(query, item.lowerName):              |    |
|   |        calculate score with bonuses                        |    |
|   |        add to candidates with score                        |    |
|   +-----------------------------------------------------------+    |
|             |                                                       |
|             v                                                       |
|   Step 5: Calculate Final Scores                                    |
|   +-----------------------------------------------------------+    |
|   | For each candidate:                                        |    |
|   |   matchScore = calculateMatchScore(query, item)            |    |
|   |   usageScore = item.usageCount * USAGE_WEIGHT              |    |
|   |   recencyBonus = calculateRecency(item.lastUsed)           |    |
|   |   finalScore = matchScore + usageScore + recencyBonus      |    |
|   +-----------------------------------------------------------+    |
|             |                                                       |
|             v                                                       |
|   Step 6: Sort and Limit                                            |
|   +-----------------------------------------------------------+    |
|   | candidates.sortByScore(descending)                         |    |
|   | return candidates.take(MAX_RESULTS)                        |    |
|   +-----------------------------------------------------------+    |
|             |                                                       |
|             v                                                       |
|   OUTPUT: [Chrome, ChromeDriver, ...]                               |
|                                                                     |
+--------------------------------------------------------------------+

6. Phased Implementation Guide

Phase 1: Static Index with Hardcoded Programs (2-3 hours)

Goal: Prove you can search a list and launch programs without filesystem complexity.

What You’ll Build:

  • Hardcoded array of 10-15 common programs
  • Basic search box GUI
  • Simple string matching
  • Launch on Enter

Steps:

  1. Create the script skeleton
    #Requires AutoHotkey v2.0
    
    ; Global variables
    AppIndex := []
    
    ; Entry point
    InitializeStaticIndex()
    Hotkey("!Space", ShowLauncher)
    Persistent
    
  2. Build hardcoded index
    InitializeStaticIndex() {
        global AppIndex
    
        ; Add common programs manually
        AppIndex.Push({
            name: "Notepad",
            path: "notepad.exe",
            lowerName: "notepad"
        })
        AppIndex.Push({
            name: "Calculator",
            path: "calc.exe",
            lowerName: "calculator"
        })
        ; Add 8-10 more programs
    }
    
  3. Create basic GUI with search and results
  4. Implement simple contains-match search
  5. Handle Enter key to launch selected item

Verification Checklist:

  • Alt+Space opens the launcher
  • Typing “note” shows Notepad
  • Pressing Enter launches the application
  • Pressing Escape closes the launcher

What You’re Learning: AHK v2 syntax, GUI creation, event handling, basic data structures.


Phase 2: Directory Scanning (3-4 hours)

Goal: Replace hardcoded list with dynamically scanned programs.

What You’ll Build:

  • Filesystem scanner function
  • Support for .exe files
  • Loading indicator during scan
  • Error handling for inaccessible folders

Steps:

  1. Create the indexer module
    BuildIndex() {
        global AppIndex
        AppIndex := []
    
        ; Show loading indicator
        ShowLoadingIndicator()
    
        ; Scan key directories
        ScanDirectory(A_ProgramFiles)
        ScanDirectory(A_ProgramFiles . " (x86)")
    
        HideLoadingIndicator()
    }
    
  2. Implement recursive scanning
    ScanDirectory(path) {
        global AppIndex
    
        if (!DirExist(path))
            return
    
        Loop Files, path "\*.exe", "R" {
            AppIndex.Push({
                name: StrReplace(A_LoopFileName, ".exe", ""),
                path: A_LoopFileFullPath,
                lowerName: StrLower(StrReplace(A_LoopFileName, ".exe", ""))
            })
        }
    }
    
  3. Add exclusion patterns (skip updaters, uninstallers)
  4. Call BuildIndex() at startup
  5. Display item count after indexing

Verification Checklist:

  • Indexing completes in < 5 seconds
  • Index contains 100+ items
  • “Indexing…” message appears during scan
  • Searching still works with dynamic index

What You’re Learning: Filesystem operations, Loop Files syntax, error handling, performance awareness.


Phase 3: Fuzzy Search Implementation (3-4 hours)

Goal: Implement intelligent string matching that handles typos and abbreviations.

What You’ll Build:

  • Scoring system for match quality
  • Subsequence matching algorithm
  • Multi-tier matching (exact > prefix > contains > fuzzy)

Steps:

  1. Create scoring function
    CalculateScore(query, target) {
        ; Exact match
        if (query == target)
            return 1000
    
        ; Prefix match
        if (SubStr(target, 1, StrLen(query)) == query)
            return 500 + (StrLen(query) / StrLen(target) * 100)
    
        ; Contains match
        if (InStr(target, query))
            return 200 + (StrLen(query) / StrLen(target) * 100)
    
        ; Fuzzy match
        return FuzzyScore(query, target)
    }
    
  2. Implement subsequence matching
    FuzzyScore(query, target) {
        queryPos := 1
        consecutiveBonus := 0
        lastMatchPos := 0
    
        Loop Parse, target {
            if (queryPos > StrLen(query))
                break
    
            if (A_LoopField == SubStr(query, queryPos, 1)) {
                if (A_Index == lastMatchPos + 1)
                    consecutiveBonus += 10
                lastMatchPos := A_Index
                queryPos++
            }
        }
    
        if (queryPos > StrLen(query))
            return 50 + consecutiveBonus
    
        return 0
    }
    
  3. Update search to use scoring
  4. Sort results by score
  5. Test with various query patterns

Verification Checklist:

  • “chrome” exactly matches “Chrome” (score ~1000)
  • “chr” matches “Chrome” as prefix (score ~550)
  • “rom” matches “Chrome” as substring (score ~250)
  • “vscd” matches “Visual Studio Code” (fuzzy)
  • Results are sorted by score

What You’re Learning: String algorithms, scoring systems, algorithm design.


Phase 4: GUI Integration (3-4 hours)

Goal: Create a polished, responsive user interface.

What You’ll Build:

  • Professional-looking search window
  • Real-time filtering with debouncing
  • Keyboard navigation
  • Visual feedback for selection

Steps:

  1. Design the GUI layout
    CreateLauncherGUI() {
        global MainGui, SearchBox, ResultsList
    
        MainGui := Gui("+AlwaysOnTop -Caption +Border", "Launcher")
        MainGui.BackColor := "1a1a2e"
        MainGui.SetFont("s12 cWhite", "Segoe UI")
    
        SearchBox := MainGui.Add("Edit", "w400 h35 Background0f0f23")
        SearchBox.OnEvent("Change", OnSearchChange)
    
        ResultsList := MainGui.Add("ListView", "w400 h350 Background0f0f23 -Hdr",
            ["Icon", "Name", "Path"])
    }
    
  2. Implement debouncing
    global DebounceTimer := 0
    
    OnSearchChange(ctrl, *) {
        global DebounceTimer
    
        if (DebounceTimer)
            SetTimer(DebounceTimer, 0)
    
        DebounceTimer := SetTimer(DoSearch, -100)
    }
    
  3. Add keyboard navigation in search box
  4. Handle focus loss and escape key
  5. Add visual polish (colors, fonts)

Verification Checklist:

  • GUI appears centered on screen
  • Typing updates results in real-time
  • Up/Down arrows navigate results from search box
  • Enter launches selected item
  • Escape closes the window
  • Clicking elsewhere closes the window

What You’re Learning: GUI design, UX patterns, event coordination, visual design.


Phase 5: Shortcut Support and Usage Tracking (4-5 hours)

Goal: Index Start Menu shortcuts and learn user preferences.

What You’ll Build:

  • .lnk file resolution using COM
  • Usage count persistence
  • Ranking that combines match quality with frequency
  • Periodic auto-save of usage data

Steps:

  1. Add shortcut scanning
    ScanShortcuts(path) {
        global AppIndex
    
        Loop Files, path "\*.lnk", "R" {
            try {
                info := ResolveShortcut(A_LoopFileFullPath)
                if (info.path != "" && FileExist(info.path)) {
                    AppIndex.Push({
                        name: StrReplace(A_LoopFileName, ".lnk", ""),
                        path: info.path,
                        args: info.args,
                        isShortcut: true,
                        lowerName: StrLower(StrReplace(A_LoopFileName, ".lnk", ""))
                    })
                }
            }
        }
    }
    
  2. Implement shortcut resolution
    ResolveShortcut(lnkPath) {
        shell := ComObject("WScript.Shell")
        shortcut := shell.CreateShortcut(lnkPath)
        return {
            path: shortcut.TargetPath,
            args: shortcut.Arguments
        }
    }
    
  3. Add usage tracking
    TrackUsage(item) {
        item.usageCount++
        item.lastUsed := A_Now
        ScheduleUsageSave()
    }
    
  4. Persist usage data to JSON or INI
  5. Load usage data on startup
  6. Incorporate usage into scoring

Verification Checklist:

  • Start Menu shortcuts appear in search
  • Launching an app increments its usage count
  • Usage data survives script restart
  • Frequently used apps rank higher for same query

What You’re Learning: COM automation, data persistence, machine learning basics (frequency learning).


Phase 6: Polish and Production Readiness (3-4 hours)

Goal: Create a tool you’ll use every day.

What You’ll Build:

  • Application icons in results
  • Re-index functionality
  • Configuration file support
  • Error handling and edge cases
  • System tray integration

Steps:

  1. Add application icons
    ; Icons require ImageList and ListView integration
    ImageListID := IL_Create(100)
    LV_SetImageList(ImageListID)
    
    ; Extract icon from exe
    iconIndex := IL_Add(ImageListID, item.path, 1)
    
  2. Create configuration system
    LoadConfig() {
        global Settings
        configPath := A_AppData "\Launcher\config.ini"
    
        Settings := {
            hotkey: IniRead(configPath, "General", "Hotkey", "!Space"),
            maxResults: IniRead(configPath, "General", "MaxResults", 10),
            debounceMs: IniRead(configPath, "General", "DebounceMs", 100)
        }
    }
    
  3. Add re-index hotkey (Ctrl+Shift+R while open)
  4. Handle edge cases:
    • Missing/moved executables
    • Very long paths
    • Permission errors
    • Network paths (skip or timeout)
  5. Add system tray icon with menu
  6. Create startup shortcut option

Verification Checklist:

  • Icons appear next to application names
  • Re-indexing works without restart
  • Configuration file is created and respected
  • System tray icon with “Exit” and “Re-index” options
  • Graceful handling of missing files

What You’re Learning: Production software considerations, configuration management, user experience polish.


7. Testing Strategy

Unit Tests

Test ID Function Input Expected Output
U1 CalculateScore (“chrome”, “chrome”) 1000 (exact)
U2 CalculateScore (“chr”, “chrome”) ~550 (prefix)
U3 CalculateScore (“rom”, “chrome”) ~250 (contains)
U4 CalculateScore (“crm”, “chrome”) ~70 (fuzzy)
U5 CalculateScore (“xyz”, “chrome”) 0 (no match)
U6 FuzzyScore (“vscd”, “visual studio code”) > 0
U7 FuzzyScore (“abc”, “xyz”) 0
U8 ResolveShortcut (valid .lnk) {path: “…”, args: “…”}
U9 ResolveShortcut (invalid .lnk) Exception/empty

Test Implementation Pattern:

RunTests() {
    passed := 0
    failed := 0

    ; Test U1: Exact match
    result := CalculateScore("chrome", "chrome")
    if (result == 1000) {
        passed++
    } else {
        failed++
        OutputDebug("U1 FAILED: Expected 1000, got " . result)
    }

    ; Continue for other tests...

    MsgBox("Tests: " . passed . " passed, " . failed . " failed")
}

Integration Tests

Test ID Scenario Steps Expected
I1 Index builds Start script Index > 100 items in < 5s
I2 Search works Type “notepad” Notepad in results
I3 Fuzzy works Type “ntpd” Notepad in results
I4 Launch works Select, Enter App launches
I5 Usage tracks Launch 3x usageCount = 3
I6 Persistence Restart script Usage data preserved
I7 Shortcuts Type “Word” MS Word (from Start Menu)
I8 Icons View results Icons display correctly

Performance Tests

Test ID Metric Threshold How to Measure
P1 Index build (1000 items) < 2s A_TickCount before/after
P2 Index build (5000 items) < 5s A_TickCount before/after
P3 Search response < 50ms Timestamp in OnSearchChange vs results displayed
P4 GUI show < 100ms Timestamp in hotkey handler
P5 Memory usage < 100MB Task Manager

Edge Case Tests

Test ID Scenario Steps Expected
E1 Empty query Clear search box Show recent/all
E2 Very long query Type 100+ chars No crash, truncate
E3 Special characters Type “c:\” Handled gracefully
E4 Missing file Indexed file deleted Skip or show error
E5 No matches Type “xyzxyz” Empty results, no crash
E6 Network path Scan network folder Timeout, don’t hang
E7 Unicode Search for emoji app Works correctly

8. Common Pitfalls and Debugging Tips

Pitfall 1: Index Takes Too Long

Symptoms:

  • Script hangs on startup
  • “Not Responding” in title bar
  • User gives up waiting

Causes:

  1. Scanning too many directories (entire C: drive)
  2. Including network paths
  3. Antivirus scanning each file access
  4. Very deep folder structures

Solutions:

; 1. Limit scan depth
Loop Files, path "\*.exe", "R" {
    ; Skip if too deep
    if (StrLen(A_LoopFileDir) - StrLen(path) > 100)
        continue
    ; ...
}

; 2. Skip known slow paths
if (InStr(A_LoopFileDir, "node_modules") ||
    InStr(A_LoopFileDir, ".git") ||
    InStr(A_LoopFileDir, "temp"))
    continue

; 3. Add timeout for network paths
if (SubStr(path, 1, 2) == "\\")  ; UNC path
    return  ; Skip entirely

; 4. Show progress
SplashGui.Add("Text", , "Scanning: " . path)

Pitfall 2: Search Feels Laggy

Symptoms:

  • Noticeable delay after typing
  • UI stutters during search
  • Characters appear delayed

Causes:

  1. Searching on every keystroke without debouncing
  2. Expensive operations in search loop
  3. Too many items being processed
  4. Updating GUI too frequently

Solutions:

; 1. Implement debouncing (essential!)
global DebounceTimer := 0

OnSearchChange(ctrl, *) {
    global DebounceTimer
    if (DebounceTimer)
        SetTimer(DebounceTimer, 0)
    DebounceTimer := SetTimer(DoSearch, -100)  ; Wait 100ms
}

; 2. Early termination
Search(query) {
    results := []
    for item in AppIndex {
        if (results.Length >= 50)  ; Got enough candidates
            break
        ; ...
    }
}

; 3. Pre-compute lowercase names at index time, not search time
; BAD:
if (InStr(StrLower(item.name), query))  ; Called every search!

; GOOD:
if (InStr(item.lowerName, query))  ; Pre-computed once

Pitfall 3: Apps Don’t Launch

Symptoms:

  • Enter is pressed but nothing happens
  • Error message about file not found
  • Wrong application opens

Causes:

  1. Path has spaces but not quoted
  2. Shortcut target no longer exists
  3. Requires administrator privileges
  4. Path stored incorrectly

Solutions:

; 1. Always handle paths with spaces
LaunchApp(item) {
    path := item.path

    ; Quote if contains spaces
    if (InStr(path, " "))
        path := '"' . path . '"'

    ; Add arguments if present
    if (item.HasProp("args") && item.args != "")
        path .= " " . item.args

    try {
        Run(path)
    } catch Error as e {
        MsgBox("Failed to launch: " . e.Message . "`nPath: " . item.path)
    }
}

; 2. Verify file exists before adding to index
if (!FileExist(info.path)) {
    ; Skip this shortcut - target missing
    continue
}

; 3. Handle admin requirements
try {
    Run(path)
} catch Error as e {
    if (InStr(e.Message, "requires elevation")) {
        ; Offer to run as admin
        result := MsgBox("Run as administrator?", , "YesNo")
        if (result == "Yes")
            Run("*RunAs " . path)
    }
}

Pitfall 4: Shortcuts Not Resolving

Symptoms:

  • Start Menu shortcuts missing from index
  • Error when processing .lnk files
  • Empty path returned

Causes:

  1. COM object not created properly
  2. Shortcut file is corrupted
  3. Target is a UWP/Store app (different format)
  4. Insufficient permissions

Solutions:

ResolveShortcut(lnkPath) {
    try {
        shell := ComObject("WScript.Shell")
        shortcut := shell.CreateShortcut(lnkPath)

        target := shortcut.TargetPath

        ; Handle empty target (might be UWP app)
        if (target == "") {
            ; UWP apps have special handling
            return {path: "", args: "", isUWP: true}
        }

        ; Verify target exists
        if (!FileExist(target)) {
            return {path: "", args: ""}
        }

        return {
            path: target,
            args: shortcut.Arguments
        }
    } catch Error as e {
        ; Log but don't crash
        OutputDebug("Failed to resolve: " . lnkPath . " - " . e.Message)
        return {path: "", args: ""}
    }
}

Pitfall 5: Usage Data Not Persisting

Symptoms:

  • Frequently used apps don’t rise in ranking
  • Usage resets on script restart
  • Data file is empty or corrupted

Causes:

  1. File path doesn’t exist
  2. Writing before directory is created
  3. File locked by another process
  4. JSON/INI format errors

Solutions:

SaveUsageData() {
    global AppIndex

    ; 1. Ensure directory exists
    dataDir := A_AppData . "\Launcher"
    if (!DirExist(dataDir))
        DirCreate(dataDir)

    dataFile := dataDir . "\usage.json"

    ; 2. Build data structure
    data := Map()
    for item in AppIndex {
        if (item.usageCount > 0) {
            data[item.path] := {
                count: item.usageCount,
                lastUsed: item.lastUsed
            }
        }
    }

    ; 3. Write with error handling
    try {
        ; Delete old file first
        if (FileExist(dataFile))
            FileDelete(dataFile)

        ; Write new data
        jsonStr := JSON.Stringify(data)  ; Assuming JSON library
        FileAppend(jsonStr, dataFile, "UTF-8")
    } catch Error as e {
        OutputDebug("Failed to save usage data: " . e.Message)
    }
}

Debugging Techniques

1. Use OutputDebug for tracing:

OutputDebug("Index size: " . AppIndex.Length)
OutputDebug("Search query: [" . query . "]")
OutputDebug("Match score for " . item.name . ": " . score)

View in DebugView or VS Code with AHK extension.

2. Add timing measurements:

startTime := A_TickCount

; ... operation ...

elapsed := A_TickCount - startTime
OutputDebug("Operation took " . elapsed . "ms")

3. Create a debug mode:

global DebugMode := true

DebugLog(msg) {
    global DebugMode
    if (DebugMode)
        OutputDebug("[Launcher] " . msg)
}

4. Add a debug panel:

; Show debug info in GUI when Ctrl is held
if (GetKeyState("Control")) {
    ResultsList.Add(["DEBUG", "Index: " . AppIndex.Length . " items"])
    ResultsList.Add(["DEBUG", "Query: " . query])
    ResultsList.Add(["DEBUG", "Results: " . results.Length])
}

9. Extensions and Challenges

Easy Extensions (1-2 hours each)

  1. Configurable Hotkey
    • Read hotkey from config file
    • Allow runtime changing via tray menu
  2. Theme Support
    • Dark mode (default)
    • Light mode option
    • Custom color schemes
  3. Clear Recent
    • Button or hotkey to reset usage data
    • Useful when preferences change
  4. Search Highlighting
    • Highlight matched characters in results
    • Show which part of name matched

Medium Extensions (3-5 hours each)

  1. Application Icons
    • Extract icons from .exe files
    • Display in ListView with ImageList
    • Cache icons for performance
  2. File/Folder Search
    • Search recent documents
    • Index common folders (Desktop, Documents)
    • Open folder in Explorer option
  3. Web Search Fallback
    • If no local matches, offer Google/Bing search
    • Detect URLs and open directly
  4. Calculator Mode
    • Detect math expressions (starts with “=”)
    • Evaluate and show result
    • Copy result to clipboard
  5. Multiple Selection
    • Shift+Enter to launch multiple apps
    • Ctrl+Click to toggle selection

Advanced Extensions (1-2 days each)

  1. Plugin System
    • Define plugin interface
    • Load external .ahk files as plugins
    • Plugins provide custom result types
  2. Clipboard Integration
    • Search clipboard history
    • Paste to active window option
    • Connect with P01 (Clipboard Manager)
  3. Window Switcher
    • Search open windows by title
    • Switch to or close windows
    • Preview window thumbnails
  4. Custom Commands
    • Define shortcuts like “email -> Open Gmail”
    • Support aliases and macros
    • Parameterized commands
  5. Learning Algorithm
    • Track search-to-launch patterns
    • Predict most likely app for partial queries
    • Time-of-day weighting
  6. Sync Across Machines
    • Export/import configuration
    • Cloud sync for usage data
    • Shared custom commands

10. Books That Will Help

Topic Book Chapter/Section Why It Helps
String Searching Algorithms Algorithms, Fourth Edition by Robert Sedgewick Chapter 5.3: Substring Search Understand KMP, Boyer-Moore, and Rabin-Karp algorithms for efficient string matching
Edit Distance Introduction to Algorithms (CLRS) Chapter 15: Dynamic Programming (specifically 15.4) Deep dive into Levenshtein distance and optimal substructure
Data Structures for Search Algorithms, Fourth Edition by Robert Sedgewick Chapter 5.2: Tries Learn trie/prefix tree implementation for O(m) prefix lookup
Caching Strategies Designing Data-Intensive Applications by Martin Kleppmann Chapter 11: Stream Processing (caching section) Understand write-through, write-back, and cache invalidation
Hash Tables Designing Data-Intensive Applications by Martin Kleppmann Chapter 3: Storage and Retrieval Hash indexes, LSM trees, and efficient lookup structures
Event Loops Game Programming Patterns by Robert Nystrom Game Loop chapter Understand main loop architecture, fixed vs variable timestep
Observer Pattern Game Programming Patterns by Robert Nystrom Observer chapter Decouple event sources from handlers (clipboard changes, hotkeys)
GUI Responsiveness The Art of UNIX Programming by Eric S. Raymond Chapter 11: Interfaces Philosophy of interface design and user responsiveness
Usability Don’t Make Me Think by Steve Krug Chapters 1-4 Launcher UX design principles, obvious vs clever
Search UX Designing the Search Experience by Tony Russell-Rose Chapters 3-5 How users search, result ranking, and display patterns
Windows Internals Windows Internals, Part 1 by Yosifovich et al. Chapter 3: System Mechanisms Understand Windows message loop, window procedures
Windows Internals Windows Security Internals by James Forshaw Chapter 2: User Mode Filesystem, registry, and special folders explained

Reading Priority:

  1. Algorithms, Fourth Edition - Core algorithms knowledge (Chapters 5.2-5.3)
  2. Game Programming Patterns - Event-driven architecture concepts
  3. Don’t Make Me Think - UX principles for launcher design
  4. Designing Data-Intensive Applications - Caching and performance

11. Self-Assessment Checklist

Before considering this project complete, verify each item:

Core Functionality

  • Index builds automatically on startup
    • Scans Program Files, Program Files (x86), Start Menu
    • Completes in < 5 seconds
    • Shows loading indicator during scan
  • At least 200 applications are indexed
    • Includes .exe files from Program Files
    • Includes .lnk files from Start Menu
    • No duplicates (same path)
  • Search results appear in < 100ms after typing stops
    • Debouncing prevents excessive searches
    • Pre-computed lowercase names
    • Efficient scoring algorithm
  • Fuzzy matching catches common patterns
    • “vscd” finds “Visual Studio Code”
    • “ff” finds “Firefox”
    • “chrme” finds “Chrome” (typo tolerance)

User Experience

  • GUI is polished and professional
    • Clean visual design
    • Readable fonts and colors
    • Proper spacing and alignment
  • Keyboard navigation works completely
    • Up/Down arrows in search box move selection
    • Enter launches selected item
    • Escape closes window
    • Tab navigates between controls
  • Focus behavior is correct
    • Search box focused when window opens
    • Clicking elsewhere closes window
    • Focus returns to previous window after close

Data Management

  • Selected application launches correctly
    • Handles paths with spaces
    • Respects arguments from shortcuts
    • Shows error for missing files
  • Shortcut (.lnk) files resolve properly
    • Target path extracted via COM
    • Arguments preserved
    • Invalid shortcuts skipped gracefully
  • Usage tracking affects result ranking
    • Launch count increments on each launch
    • More frequent apps rank higher
    • Ranking combines match score and usage
  • Usage data persists across restarts
    • Data saved to file
    • Loaded on startup
    • No corruption on crash

Reliability

  • Handles missing/moved applications gracefully
    • No crash if indexed file is deleted
    • Optionally shows “Not Found” message
    • Can re-index to refresh
  • Works without administrator privileges
    • Uses user-writable paths for data
    • Scans only accessible folders
    • Gracefully skips restricted areas
  • No memory leaks during extended use
    • Memory stays stable after hours of use
    • GUI properly destroyed on close
    • Event handlers cleaned up

Performance

  • Index build time < 5 seconds
  • Search response < 50ms
  • GUI show time < 100ms
  • Memory usage < 100MB

Code Quality

  • Code is organized into logical functions
  • Variables have meaningful names
  • Complex logic is commented
  • Error handling is comprehensive
  • No hardcoded paths (uses environment variables)

Summary

This project teaches you to build a productivity tool that combines:

  1. Systems Programming - Filesystem traversal, Windows APIs, COM automation
  2. Algorithm Design - Fuzzy matching, scoring systems, optimization
  3. User Interface Design - Responsive GUIs, keyboard navigation, visual feedback
  4. Data Persistence - Saving/loading state, learning from user behavior
  5. Performance Engineering - Caching, debouncing, profiling

When complete, you’ll have a launcher that rivals commercial tools like Alfred or Raycast, demonstrating real software engineering skills applicable to any platform or language.

Total estimated time: 20-30 hours across 2-3 weeks

Difficulty progression:

  • Phases 1-2: Fundamentals (approachable)
  • Phases 3-4: Core features (moderate challenge)
  • Phases 5-6: Polish and production (advanced topics)

Good luck, and enjoy building something you’ll use every day!