Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions pylsp/uris.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ def from_fs_path(path):
return urlunparse((scheme, netloc, path, params, query, fragment))


def normalize(uri):
"""Return a canonical form of the given URI.

Windows drive letters are case-insensitive, so clients may send either
``file:///C:/foo`` or ``file:///c:/foo`` for the same file. Both forms
normalize to the lower-case one used by :func:`from_fs_path`, so that a
URI can be used as a stable dictionary key.
"""
scheme, netloc, path, params, query, fragment = urlparse(uri)
if RE_DRIVE_LETTER_PATH.match(path):
path = path[0] + path[1].lower() + path[2:]
return urlunparse((scheme, netloc, path, params, query, fragment))


def uri_with(
uri, scheme=None, netloc=None, path=None, params=None, query=None, fragment=None
):
Expand Down
24 changes: 12 additions & 12 deletions pylsp/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,23 +112,23 @@ def get_document(self, doc_uri):

See https://github.com/Microsoft/language-server-protocol/issues/177
"""
return self._docs.get(doc_uri) or self._create_document(doc_uri)
return self._docs.get(uris.normalize(doc_uri)) or self._create_document(doc_uri)

def get_cell_document(self, doc_uri):
return self._docs.get(doc_uri)
return self._docs.get(uris.normalize(doc_uri))

def get_maybe_document(self, doc_uri):
return self._docs.get(doc_uri)
return self._docs.get(uris.normalize(doc_uri))

def put_document(self, doc_uri, source, version=None) -> None:
self._docs[doc_uri] = self._create_document(
self._docs[uris.normalize(doc_uri)] = self._create_document(
doc_uri, source=source, version=version
)

def put_notebook_document(
self, doc_uri, notebook_type, cells, version=None, metadata=None
) -> None:
self._docs[doc_uri] = self._create_notebook_document(
self._docs[uris.normalize(doc_uri)] = self._create_notebook_document(
doc_uri, notebook_type, cells, version, metadata
)

Expand All @@ -144,27 +144,27 @@ def temp_document(self, source, path=None) -> None:
self.rm_document(uri)

def add_notebook_cells(self, doc_uri, cells, start) -> None:
self._docs[doc_uri].add_cells(cells, start)
self._docs[uris.normalize(doc_uri)].add_cells(cells, start)

def remove_notebook_cells(self, doc_uri, start, delete_count) -> None:
self._docs[doc_uri].remove_cells(start, delete_count)
self._docs[uris.normalize(doc_uri)].remove_cells(start, delete_count)

def update_notebook_metadata(self, doc_uri, metadata) -> None:
self._docs[doc_uri].metadata = metadata
self._docs[uris.normalize(doc_uri)].metadata = metadata

def put_cell_document(
self, doc_uri, notebook_uri, language_id, source, version=None
) -> None:
self._docs[doc_uri] = self._create_cell_document(
self._docs[uris.normalize(doc_uri)] = self._create_cell_document(
doc_uri, notebook_uri, language_id, source, version
)

def rm_document(self, doc_uri) -> None:
self._docs.pop(doc_uri)
self._docs.pop(uris.normalize(doc_uri))

def update_document(self, doc_uri, change, version=None) -> None:
self._docs[doc_uri].apply_change(change)
self._docs[doc_uri].version = version
self._docs[uris.normalize(doc_uri)].apply_change(change)
self._docs[uris.normalize(doc_uri)].version = version

def update_config(self, settings):
self._config.update((settings or {}).get("pylsp", {}))
Expand Down
13 changes: 13 additions & 0 deletions test/test_uris.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,16 @@ def test_win_from_fs_path(path, uri) -> None:
)
def test_uri_with(uri, kwargs, new_uri) -> None:
assert uris.uri_with(uri, **kwargs) == new_uri


@pytest.mark.parametrize(
"uri,normalized",
[
("file:///C:/far/boo", "file:///c:/far/boo"),
("file:///c:/far/boo", "file:///c:/far/boo"),
("file:///C:/far/space%20%3Fboo", "file:///c:/far/space%20%3Fboo"),
("file:///foo/bar", "file:///foo/bar"),
],
)
def test_normalize(uri, normalized) -> None:
assert uris.normalize(uri) == normalized
11 changes: 11 additions & 0 deletions test/test_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,14 @@ class DummyError(Exception):
{"kind": "begin", "title": "some_title"},
{"kind": "end"},
]


def test_put_document_normalizes_drive_letter_case(pylsp) -> None:
"""A drive letter is case-insensitive, so both spellings are one document."""
upper_uri = "file:///C:/far/boo.py"
lower_uri = "file:///c:/far/boo.py"

pylsp.workspace.put_document(upper_uri, "content")

assert pylsp.workspace.get_maybe_document(lower_uri) is not None
assert pylsp.workspace.get_document(lower_uri).source == "content"
Loading