Skip to content

[Detail Bug] Windows: Lowercase drive letters in URIs fail to match uppercase routes (workspace config ignored) #294

@detail-app

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_4c5b920c-9e04-4530-a4d2-953927859978/bugs/bug_709a16bd-e80f-4e1b-a22e-d824a08546d3

Summary

  • Context: The get_drives() function in crates/typos-lsp/src/windows.rs returns Windows drive letters that are used to create router routes for matching file URIs to their corresponding workspace configurations.
  • Bug: The function returns uppercase drive letters (A, B, C, etc.), but LSP clients may send file URIs with lowercase drive letters (a, b, c, etc.), and the matchit router performs case-sensitive matching.
  • Actual vs. expected: When a client sends a URI with a lowercase drive letter (e.g., file:///c:/Users/file.txt), it fails to match routes created with uppercase letters (e.g., /C%3A/{*p}), causing the LSP server to fall back to the default policy instead of using the workspace-specific configuration.
  • Impact: Files opened on Windows with clients that use lowercase drive letters (such as VS Code) will not use workspace-specific typos configuration files (e.g., _typos.toml), instead falling back to default settings, which may miss custom dictionaries, ignore patterns, or other project-specific settings.

Code with bug

#[cfg(windows)]
pub fn get_drives() -> Vec<String> {
    let mut drives = Vec::new();
    let mut bitmask = unsafe { GetLogicalDrives() };
    let mut letter = b'A';  // <-- BUG 🔴 Always starts with uppercase 'A'
    while bitmask > 0 {
        if bitmask & 1 != 0 {
            drives.push((letter as char).into());  // <-- BUG 🔴 Pushes uppercase letters
        }
        bitmask >>= 1;
        letter += 1;
    }
    drives
}

In crates/typos-lsp/src/state.rs:

#[cfg(windows)]
for drive in crate::windows::get_drives() {
    let route = format!("/{}%3A/{{*p}}", &drive);  // <-- Creates routes like /C%3A/{*p}
    self.router.insert_instance(
        &route,
        &PathBuf::from(format!("{}:\\", &drive)),
        self.config.as_deref(),
    )?;
}

Example

  1. Route creation: get_drives() returns ["C", "D"], creating routes /C%3A/{*p} and /D%3A/{*p}.
  2. Client sends URI: file:///c:/Users/project/file.txt (lowercase c).
  3. URI is sanitized to /c%3A/Users/project/file.txt.
  4. Case-sensitive router fails to match /c%3A/... against /C%3A/{*p}.
  5. Server falls back to default policy instead of workspace configuration.

Expected: The file should use the workspace configuration for the C: drive.

Recommended fix

Register both uppercase and lowercase drive routes so matching is case-agnostic:

#[cfg(windows)]
pub fn get_drives() -> Vec<String> {
    let mut drives = Vec::new();
    let mut bitmask = unsafe { GetLogicalDrives() };
    let mut letter = b'A';
    while bitmask > 0 {
        if bitmask & 1 != 0 {
            drives.push((letter as char).to_string());  // uppercase
            drives.push((letter as char).to_lowercase().to_string());  // <-- FIX 🟢 Add lowercase
        }
        bitmask >>= 1;
        letter += 1;
    }
    drives
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions