URI Normalization Inconsistency Causes Workspace Cache Misses on Windows
Summary
pylsp-rope refactoring operations fail on Windows when the LSP client sends URIs with uppercase drive letters (RFC 8089 standard file:///C:/). The root cause is inconsistent URI normalization: uris.from_fs_path() normalizes drive letters to lowercase, but workspace.put_document() stores URIs without normalization, causing dictionary key mismatches.
Environment
- OS: Windows 10/11
- pylsp version: 1.14.0
- pylsp-rope version: 0.1.17
- LSP Client: Any client following RFC 8089 standard (uppercase drive letters)
Steps to Reproduce
- Setup: Use an LSP client that sends RFC 8089 compliant URIs with uppercase drive letters (
file:///C:/)
- Open: A Python file on Windows
- First refactoring: Perform any refactoring operation (e.g., "Extract Variable") - works fine
- DO NOT SAVE the file - leave it with unsaved changes from the refactoring
- Second refactoring: Attempt another refactoring operation on the same file
- Observe: Second refactoring fails because it operates on the old saved content from disk instead of the current unsaved content in workspace
Root Cause Analysis
The Problem
File: pylsp/uris.py (lines 135-137)
# Normalize drive paths to lower case
if RE_DRIVE_LETTER_PATH.match(path):
path = path[0] + path[1].lower() + path[2:]
This function normalizes Windows drive letters to lowercase (C: → c:).
File: pylsp/workspace.py
put_document() stores URIs exactly as received from client (NO normalization)
get_maybe_document() retrieves URIs exactly as provided (NO normalization)
The Flow That Breaks
- Client sends didOpen:
file:///C:/test.py (uppercase C, RFC 8089 standard)
- pylsp stores:
workspace._docs["file:///C:/test.py"] (uppercase, as received)
- pylsp-rope needs file content:
- Calls
uris.from_fs_path("C:\\test.py")
- Returns
file:///c:/test.py (lowercase c, normalized)
- Calls
workspace.get_maybe_document("file:///c:/test.py")
- Returns
None (key mismatch: "C:" vs "c:")
- Fallback: Reads from filesystem (stale content)
- Result: Refactoring operates on outdated code
Evidence from Logs
Server log shows the problem:
pylsp_rope.project - reading from filesystem: "C:\Users\...\file.py"
Instead of the expected:
pylsp_rope.project - reading from workspace: "C:\Users\...\file.py"
Client-server communication:
Client → Server (didOpen): file:///C:/Users/test.py
Server stores in _docs: file:///C:/Users/test.py (uppercase)
Rope generates URI: file:///c:/Users/test.py (lowercase)
Workspace lookup: _docs.get("file:///c:/...") → None
Impact
- Subsequent refactoring operations fail when file has unsaved changes from previous refactoring
- First refactoring works, but second/third/etc. fail because they operate on stale disk content
- Affects all Windows users using LSP clients that follow RFC 8089 (uppercase drive letters)
- Works by accident if client sends lowercase drive letters (non-standard)
- Silent failure: No error message, refactoring just produces incorrect results based on old content
Affected LSP Clients
- Any LSP client following RFC 8089 standard (uppercase drive letters)
- Potentially: Sublime Text, Vim/Neovim, Emacs, custom editors
- VSCode likely unaffected (may send lowercase URIs)
Cross-Platform Considerations
⚠️ CRITICAL: Any fix must preserve case-sensitivity on Linux/macOS where filesystems are case-sensitive. The normalization should ONLY affect Windows drive letters (C: vs c:), not entire paths.
Safe on Windows: C:\Users\File.py and c:\users\file.py refer to the same file (case-insensitive filesystem)
NOT safe on Linux: /home/User/File.py and /home/user/file.py are DIFFERENT files (case-sensitive filesystem)
Workaround for Users
LSP client developers can work around this by normalizing drive letters to lowercase before sending URIs to pylsp:
# Client-side workaround (Python example)
if sys.platform == 'win32' and uri[8:9].isupper():
uri = f"{uri[:8]}{uri[8].lower()}{uri[9:]}"
However, this violates RFC 8089 standard and should not be necessary.
Related Issues
This may also affect other pylsp functionality that relies on workspace document cache, not just pylsp-rope.
References
Thank you for maintaining pylsp! This is a subtle but impactful bug that affects Windows users following LSP standards. Happy to provide additional information or testing if needed.
URI Normalization Inconsistency Causes Workspace Cache Misses on Windows
Summary
pylsp-rope refactoring operations fail on Windows when the LSP client sends URIs with uppercase drive letters (RFC 8089 standard
file:///C:/). The root cause is inconsistent URI normalization:uris.from_fs_path()normalizes drive letters to lowercase, butworkspace.put_document()stores URIs without normalization, causing dictionary key mismatches.Environment
Steps to Reproduce
file:///C:/)Root Cause Analysis
The Problem
File:
pylsp/uris.py(lines 135-137)This function normalizes Windows drive letters to lowercase (
C:→c:).File:
pylsp/workspace.pyput_document()stores URIs exactly as received from client (NO normalization)get_maybe_document()retrieves URIs exactly as provided (NO normalization)The Flow That Breaks
file:///C:/test.py(uppercase C, RFC 8089 standard)workspace._docs["file:///C:/test.py"](uppercase, as received)uris.from_fs_path("C:\\test.py")file:///c:/test.py(lowercase c, normalized)workspace.get_maybe_document("file:///c:/test.py")None(key mismatch:"C:"vs"c:")Evidence from Logs
Server log shows the problem:
Instead of the expected:
Client-server communication:
Impact
Affected LSP Clients
Cross-Platform Considerations
C:vsc:), not entire paths.Safe on Windows:
C:\Users\File.pyandc:\users\file.pyrefer to the same file (case-insensitive filesystem)NOT safe on Linux:
/home/User/File.pyand/home/user/file.pyare DIFFERENT files (case-sensitive filesystem)Workaround for Users
LSP client developers can work around this by normalizing drive letters to lowercase before sending URIs to pylsp:
However, this violates RFC 8089 standard and should not be necessary.
Related Issues
This may also affect other pylsp functionality that relies on workspace document cache, not just pylsp-rope.
References
pathlib.Path.as_uri()generates uppercase drive letters on Windows (standard behavior)Thank you for maintaining pylsp! This is a subtle but impactful bug that affects Windows users following LSP standards. Happy to provide additional information or testing if needed.