Skip to content

Conversation

@maximsch2
Copy link

Issue I'm seeing:
I open hx /symlinked/path.py then go to definition jumps to another fully resolved path, but if I do "hx realpath $PATH" things then work fine.
My project structure: I have ~/projects/foo as my git repo root and helix workspace, but actually ~/projects is a symlink to /data/projects. Go to definition in LSP (pyright) ends up pointing to /data/projects which ends up being open in a separate tab

…te tabs

When an LSP server (e.g., pyright) returns a URI for a file that was
originally opened via a symlink, it often returns the resolved,
canonical URI. Previously, Helix's conversion from this `file://` URI
to an internal `helix-core::Uri` used a normalization function that
did not fully resolve symlinks.

This could lead to a mismatch:
1. You open `/symlink/file.py`. Helix stores its path, canonicalized
   to `/real/path/to/file.py`.
2. LSP "go to definition" returns URI for `/real/path/to/file.py`.
3. Helix converts this URI. If this conversion doesn't also result in
   the exact same canonical form as in step 1 (due to not resolving
   the symlink part if the LSP URI was subtly different but still pointed
   canonically to the same file, or if the internal URI representation
   wasn't consistently canonical), Helix might not find the existing
   document.
4. This would result in Helix opening the same file content in a new
   tab, treating it as a distinct file.

This change modifies `helix-core/src/uri.rs` to use
`helix_stdx::path::canonicalize` when converting `file://` URLs to
`helix-core::Uri::File`. This ensures that the path stored in the
`Uri` is the fully resolved, canonical path.

Combined with `Document::set_path` already storing canonical paths for
documents, this change ensures that URIs from LSPs and internal document
URIs are both in a consistent, canonical form, allowing Helix to
correctly identify and reuse existing buffers for symlinked files.

I attempted to add an integration test, but I was hindered by
compilation timeouts in the testing environment.
This commit addresses an issue where Helix could open duplicate tabs for
the same file if that file was accessed via a symlinked path and an LSP
server (e.g., pyright) returned a fully resolved (canonical) path for
"go to definition" or similar actions. This was particularly problematic
when the entire workspace root was under a symlink.

I've implemented the following changes:

1.  **Canonicalize LSP Client Root URIs (`helix-lsp`):**
    - I modified `helix-lsp/src/lib.rs` (specifically `find_lsp_workspace`
      and `start_client`) and `helix-lsp/src/client.rs`
      (`Client::try_add_doc`) to ensure that the LSP client's
      `root_path`, `root_uri`, and `workspace_folders` are stored in
      their fully canonicalized (symlink-resolved) forms. This ensures
      the LSP client operates with a canonical understanding of its
      workspace root(s).

2.  **Canonicalize Incoming LSP URIs (`helix-core`):**
    - I modified `helix-core/src/uri.rs` in the `convert_url_to_uri`
      function. When a `url::Url` with a `file://` scheme is converted
      to a `Uri::File`, the path is now processed using
      `helix_stdx::path::canonicalize` instead of
      `helix_stdx::path::normalize`. This ensures URIs from LSP messages
      are also in canonical form.

3.  **Verified Document Path Storage (`helix-view`):**
    - I confirmed that `Document::set_path` (in `helix-view/src/document.rs`)
      already uses `helix_stdx::path::canonicalize`. This means
      `Document` objects store their paths canonically.

4.  **Verified URI Comparisons (`helix-view`):**
    - I confirmed that lookups like `Editor::document_by_path` (in
      `helix-view/src/editor.rs`) correctly compare canonical paths,
      which, due to the above changes, should ensure consistent matching.

These changes collectively ensure that paths/URIs from different sources
(your input, LSP client configuration, LSP messages) are all resolved to
their canonical forms before comparison or use in lookups, preventing
the erroneous opening of duplicate buffers for symlinked files.

I wrote an integration test (`lsp_goto_definition_symlinked_workspace` in
`helix-term/tests/symlink_lsp_workspace_test.rs`) to
specifically cover the symlinked workspace root scenario. However,
persistent compilation timeouts in the testing environment prevented
this test from being run and validated.
@maximsch2 maximsch2 closed this Aug 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant