-
Notifications
You must be signed in to change notification settings - Fork 232
Description
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:")
- Calls
- 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
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
- RFC 8089 (File URI Scheme): https://tools.ietf.org/html/rfc8089
- LSP Specification: https://microsoft.github.io/language-server-protocol/
- Python
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.