# languages: Fix Python LSP workspace folder detection in uv workspaces \(\#53781\) · gitcafe/zed

[View on GitCafe](https://git.cafe/gitcafe/zed/commit/03414d805867269300fcd2f83f3863531ab69804)

Repository: [gitcafe/zed](https://git.cafe/gitcafe/zed)

Visibility: public

Requested revision: 03414d805867269300fcd2f83f3863531ab69804

Requested commit: 03414d805867269300fcd2f83f3863531ab69804

Commit: 03414d805867269300fcd2f83f3863531ab69804

Tree: f7a63c7606ad5058d3d218a65e6d89536d27e3b7

Author: Arthur Fournier

Committer: GitHub

## Message

```
languages: Fix Python LSP workspace folder detection in uv workspaces (#53781)

Closes #47926

  ## Summary

When using a uv (or Poetry/PDM) workspace with multiple subprojects,
Python LSP servers (Pyright, Ruff, ty, etc.) are initialized with the
subproject directory as their workspace folder instead of the workspace
root. This happens because `PyprojectTomlManifestProvider::search()`
returns the first (innermost)
  `pyproject.toml` found walking up the directory tree.

  For example, in a uv workspace like:

  main-project/
  ├── pyproject.toml       # workspace root with [tool.uv.workspace]
  ├── uv.lock
  ├── packages/
  │   └── project-api/
  │       └── pyproject.toml   # subpackage
  └── projects/
      └── project-a/
          └── pyproject.toml   # subpackage

Opening a file in `packages/project-api/` would register
`packages/project-api/` as the LSP workspace folder instead of
`main-project/`.

  ## Approach

The fix uses lockfile existence as a heuristic to detect workspace
roots. The updated `search()` method walks all ancestors (similar to
`CargoManifestProvider`) and:

  - Tracks the **innermost** `pyproject.toml` as a fallback
- Tracks the **outermost** `pyproject.toml` that has a sibling lockfile
(`uv.lock`, `poetry.lock`, `pdm.lock`, or `Pipfile.lock`)
- Returns the outermost workspace root if found, otherwise falls back to
the innermost

This works within the existing `ManifestDelegate` interface (existence
checks only, no file content reading).

  | Scenario | Result |
  |---|---|
| uv workspace (root `pyproject.toml` + `uv.lock`) | Returns workspace
root |
| Poetry workspace (root `pyproject.toml` + `poetry.lock`) | Returns
workspace root |
| Simple project (single `pyproject.toml`, no lockfile) | Returns
project dir (unchanged) |
| Independent subprojects (no lockfile at any level) | Returns each
project's own dir (unchanged) |

Since the manifest provider is set at the Python **language** level,
this fix applies to all Python LSP servers (Pyright, Ruff, ty, etc.).

  ## Test plan

- [x] Added unit tests for `PyprojectTomlManifestProvider` covering all
scenarios above
- [x] Existing integration test
`test_running_multiple_instances_of_a_single_server_in_one_worktree`
passes (independent subprojects without lockfiles)
  - [x] `cargo check -p languages` compiles cleanly
- [x] Manual testing with a real uv workspace (Pyright and Ruff both
receive correct workspace root)

Release Notes:

- Fixed Python LSP servers (Pyright, Ruff, etc.) using the wrong
workspace folder in uv/Poetry/PDM workspaces with multiple subprojects.
```

## Parents

- [c6772f83cabbc108114d34f6ae1b3b11a0f17972](https://git.cafe/gitcafe/zed/commit/c6772f83cabbc108114d34f6ae1b3b11a0f17972?format=markdown)

[Source at this commit](https://git.cafe/gitcafe/zed/tree/03414d805867269300fcd2f83f3863531ab69804?format=markdown)
