Skip to content

Fix WSL local project LSP URIs#669

Open
koxudaxi wants to merge 11 commits into
mainfrom
fix/wsl-local-project-uri
Open

Fix WSL local project LSP URIs#669
koxudaxi wants to merge 11 commits into
mainfrom
fix/wsl-local-project-uri

Conversation

@koxudaxi

@koxudaxi koxudaxi commented Apr 10, 2026

Copy link
Copy Markdown
Owner

Fixes: #627

Summary by CodeRabbit

  • Bug Fixes

    • Fixed Ruff server startup failures for local WSL projects on Windows.
  • Improvements

    • Better WSL/Windows path and URI handling so files resolve correctly during initialization, linting and formatting.
    • Formatting and background runs now consistently resolve and reuse the Ruff executable.
    • Improved startup and command-line logging for server diagnostics.
  • Tests

    • Added unit tests for WSL path/URI conversions, percent-encoding, stdin filename resolution, and non-WSL fallbacks.
  • Documentation

    • Added an unreleased changelog entry documenting the WSL startup fix.
  • Chores

    • Added CI job to run Windows/WSL-path tests.

@coderabbitai

coderabbitai Bot commented Apr 10, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds WSL UNC <-> file:// URI conversion helpers, integrates them into LSP server support and Ruff execution/formatting flows on Windows, threads resolved Ruff executable through command-arg/stdin-filename logic, adds tests, logging, CI job, and a changelog entry documenting the fix.

Changes

Cohort / File(s) Summary
WSL URI Helpers
src/com/koxudaxi/ruff/RuffWslUriSupport.kt
New internal helpers: buildWslFileUri, buildWslUncPath, toWindowsWslUncPath — normalize separators, handle wsl.localhost/wsl$, percent-encoding, and convert between UNC and file:// forms.
LSP Server Integration
src/com/koxudaxi/ruff/lsp/intellij/RuffLspServerSupportProvider.kt
On Windows, getFileUri, findFileByUri, and createInitializeParams map between local/WSL URIs and UNC paths using the new helpers; adds logging helpers and logName property.
Ruff execution & stdin filename handling
src/com/koxudaxi/ruff/Ruff.kt
Resolve Ruff executable once and thread it through overloads; add executable: File? parameters; change stdin-filename logic to depend on whether the resolved executable runs in WSL; add resolveStdinFileName(...).
Formatting integration
src/com/koxudaxi/ruff/RuffApplyService.kt
Resolve executable from cache before building format args; pass resolved executable into stdin-filename and command-arg generation; early-return if resolution fails.
Language client / factory tweaks
src/com/koxudaxi/ruff/lsp/lsp4ij/RuffLanguageClient.kt, src/com/koxudaxi/ruff/lsp/lsp4ij/RuffLanguageServerFactory.kt
Constructor param made non-null for RuffLanguageClient; removed some early-return logging in RuffLanguageServerFactory; minor newline fixes.
Unit tests
testSrc/com/koxudaxi/ruff/RuffWslUriSupportTest.kt, testSrc/com/koxudaxi/ruff/RuffStdinFilenameArgsTest.kt, testSrc/RuffLspServerSupportProviderTest.kt
New tests cover UNC<->URI conversions (including wsl$, percent-encoding), resolveStdinFileName across WSL vs non-WSL, and LSP initialize params normalization/formatting.
CI workflow
.github/workflows/build.yml
Added testWindowsWslPaths job on windows-latest running targeted tests and added it as a dependency of releaseDraft.
Deps / versions
gradle/libs.versions.toml
Bumped lsp4ij version reference from 0.11.00.19.2.
Changelog
CHANGELOG.md
Added Unreleased entry documenting fix for Ruff server startup failing for local WSL projects on Windows (references PR #669).

Sequence Diagram(s)

sequenceDiagram
    participant IDE as Client/IDE
    participant LSP as Ruff LSP Provider
    participant Helpers as WSL URI Helpers
    participant FS as Filesystem/Windows UNC
    participant RuffProc as Ruff Executable

    IDE->>LSP: initialize (rootUri/rootPath)
    LSP->>Helpers: normalizeWslInitializeParams(params)
    Helpers-->>LSP: normalized params (rootPath → UNC)
    LSP->>RuffProc: resolve executable & generateCommandArgs(...)
    RuffProc-->>LSP: CommandArgs (stdin filename determined)
    IDE->>LSP: request file resolution (fileUri)
    LSP->>Helpers: buildWslUncPath(fileUri)
    Helpers-->>LSP: \\wsl.localhost\distro\path or null
    LSP->>FS: findLocalFileByPath(UNC path)
    FS-->>LSP: VirtualFile
    LSP-->>IDE: resolved VirtualFile
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Preserve Ruff format stdin args #668 — Modifies Ruff argument construction and stdin-filename assembly; closely related to the executable-resolution and stdin filename changes in this PR.

Poem

🐇 I hopped through slashes, dots, and signs,
From WSL tunnels to Windows lines.
UNC and file:// now find their way,
Paths escaped, decoded, matched today.
A rabbit cheers — the servers play!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix WSL local project LSP URIs' directly describes the main change—adding WSL URI/path conversion for LSP handling on Windows to fix workspace URL conversion failures.
Linked Issues check ✅ Passed The PR fully addresses issue #627 by implementing WSL URI/path conversion helpers and integrating them into LSP server initialization and file URI handling, enabling Ruff to start with local WSL projects.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing WSL local project LSP URIs: new WSL conversion utilities, LSP path/URI handling, stdin filename resolution for WSL, test coverage, CI validation, and dependency updates align with the stated objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/wsl-local-project-uri

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Apr 10, 2026

Copy link
Copy Markdown

Qodana for JVM

46 new problems were found

Inspection name Severity Problems
Usage of API marked for removal 🔴 Failure 3
Unused symbol 🔶 Warning 11
Incorrect string capitalization 🔶 Warning 4
Unused import directive 🔶 Warning 3
Unstable API Usage 🔶 Warning 3
Unused variable 🔶 Warning 3
Array property in data class 🔶 Warning 1
Class, interface, or method should not be extended 🔶 Warning 1
Redundant 'Companion' reference 🔶 Warning 1
Redundant nullable return type 🔶 Warning 1
Boolean literal argument without parameter name ◽️ Notice 2
Package name does not match containing directory ◽️ Notice 2
Accessor call that can be replaced with property access syntax ◽️ Notice 2
Verbose nullability and emptiness check ◽️ Notice 2
Redundant argument-based 'let' call ◽️ Notice 1
Const property naming convention ◽️ Notice 1
Lambda argument inside parentheses ◽️ Notice 1
Private property naming convention ◽️ Notice 1
Regular expression can be simplified ◽️ Notice 1
Replace empty class body ◽️ Notice 1
Call chain on collection type can be simplified ◽️ Notice 1

💡 Qodana analysis was run in the pull request mode: only the changed files were checked

View the detailed Qodana report

To be able to view the detailed Qodana report, you can either:

To get *.log files or any other Qodana artifacts, run the action with upload-result option set to true,
so that the action will upload the files as the job artifacts:

      - name: 'Qodana Scan'
        uses: JetBrains/qodana-action@v2024.3.4
        with:
          upload-result: true
Contact Qodana team

Contact us at qodana-support@jetbrains.com

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
testSrc/com/koxudaxi/ruff/RuffWslUriSupportTest.kt (1)

7-53: Add coverage for wsl$ and escaped path segments.

Current tests validate wsl.localhost well, but the implementation also supports wsl$; add that variant plus a case containing spaces/special chars to prevent URI conversion regressions.

✅ Suggested additional tests
 class RuffWslUriSupportTest {
+    `@Test`
+    fun `builds and restores URI for wsl dollar host`() {
+        val unc = """\\wsl$\Ubuntu\home\test\project"""
+        val uri = "file://wsl$/Ubuntu/home/test/project"
+        assertEquals(uri, buildWslFileUri(unc))
+        assertEquals("//wsl$/Ubuntu/home/test/project", buildWslUncPath(uri))
+        assertEquals("""\\wsl$\Ubuntu\home\test\project""", toWindowsWslUncPath("//wsl$/Ubuntu/home/test/project"))
+    }
+
+    `@Test`
+    fun `handles encoded URI segments when converting back to UNC`() {
+        assertEquals(
+            "//wsl.localhost/Ubuntu/home/test/My Project",
+            buildWslUncPath("file://wsl.localhost/Ubuntu/home/test/My%20Project")
+        )
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testSrc/com/koxudaxi/ruff/RuffWslUriSupportTest.kt` around lines 7 - 53, Add
tests to RuffWslUriSupportTest covering the alternate WSL authority "wsl$" and
path segments that require escaping (e.g., spaces or percent-encoded characters)
to ensure conversions round-trip; specifically, add assertions for
buildWslFileUri, buildWslUncPath, and toWindowsWslUncPath using inputs that use
"wsl$" instead of "wsl.localhost" and at least one path containing a space or
encoded character so the implementation in buildWslFileUri, buildWslUncPath, and
toWindowsWslUncPath is exercised for both authority variants and escaped
segments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/com/koxudaxi/ruff/lsp/intellij/RuffLspServerSupportProvider.kt`:
- Around line 93-97: The createInitializeParams() override only converts
InitializeParams.rootPath for WSL; update it to also convert
InitializeParams.rootUri and every entry in InitializeParams.workspaceFolders
using toWindowsWslUncPath() when SystemInfo.isWindows is true so the LSP client
sends WSL-UNC-compatible URIs/paths to the Ruff server; locate
createInitializeParams(), call toWindowsWslUncPath() on rootUri (if non-null)
and map/replace workspaceFolders' uri/path fields accordingly (keeping existing
null-safety and using the same conversion helper).

In `@src/com/koxudaxi/ruff/RuffWslUriSupport.kt`:
- Around line 7-11: The buildWslFileUri function constructs a file:// URI via
string interpolation which skips percent-encoding for spaces/reserved
characters; change it to construct a java.net.URI (like buildWslUncPath does) by
creating a URI with scheme "file", the detected host (host from wslHosts), and
the normalized path (ensure it begins with "/" before passing as the path
argument) so the URI constructor performs proper percent-encoding; update
buildWslFileUri to return uri.toString() (or null on failure) consistent with
buildWslUncPath.

---

Nitpick comments:
In `@testSrc/com/koxudaxi/ruff/RuffWslUriSupportTest.kt`:
- Around line 7-53: Add tests to RuffWslUriSupportTest covering the alternate
WSL authority "wsl$" and path segments that require escaping (e.g., spaces or
percent-encoded characters) to ensure conversions round-trip; specifically, add
assertions for buildWslFileUri, buildWslUncPath, and toWindowsWslUncPath using
inputs that use "wsl$" instead of "wsl.localhost" and at least one path
containing a space or encoded character so the implementation in
buildWslFileUri, buildWslUncPath, and toWindowsWslUncPath is exercised for both
authority variants and escaped segments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 54a28690-65d4-44b1-a3e6-80af85d665f9

📥 Commits

Reviewing files that changed from the base of the PR and between 054998c and 974f263.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src/com/koxudaxi/ruff/RuffWslUriSupport.kt
  • src/com/koxudaxi/ruff/lsp/intellij/RuffLspServerSupportProvider.kt
  • testSrc/com/koxudaxi/ruff/RuffWslUriSupportTest.kt

Comment thread src/com/koxudaxi/ruff/lsp/intellij/RuffLspServerSupportProvider.kt Outdated
Comment thread src/com/koxudaxi/ruff/RuffWslUriSupport.kt Outdated
@angelozerr

Copy link
Copy Markdown

@koxudaxi lsp4ij 0.19.2 should support wsl, right?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/com/koxudaxi/ruff/Ruff.kt`:
- Around line 722-731: The current resolveStdinFileName may return the original
filePath when executableRunsInWsl but wslPath is null, which can leave a
Windows/UNC path into WSL; change the branch in resolveStdinFileName (and the
similar code block around the other occurrence noted) so that when
executableRunsInWsl and wslPath is null it falls back to projectRelativeFilePath
(not filePath); locate resolveStdinFileName and the analogous logic that handles
getWslPath failures and replace the fallback return value to use
projectRelativeFilePath (or null if that is also null) to ensure Ruff receives a
WSL-valid stdin filename.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 41f8cf8a-0839-41bc-8e77-3d0196046372

📥 Commits

Reviewing files that changed from the base of the PR and between 3d85de4 and e9e97b5.

📒 Files selected for processing (3)
  • src/com/koxudaxi/ruff/Ruff.kt
  • src/com/koxudaxi/ruff/RuffApplyService.kt
  • testSrc/com/koxudaxi/ruff/RuffStdinFilenameArgsTest.kt

Comment thread src/com/koxudaxi/ruff/Ruff.kt
@koxudaxi

Copy link
Copy Markdown
Owner Author

@angelozerr
Thank you for sharing the information, so I bumped the minimum supported LSP4IJ version in this plugin to 0.19.2.

The WSL URI/path conversion in this PR is for the IntelliJ built-in LSP client path only, not for the LSP4IJ client path. For LSP4IJ, the client-side WSL handling should already be covered by LSP4IJ itself.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/com/koxudaxi/ruff/lsp/intellij/RuffLspServerSupportProvider.kt`:
- Around line 121-130: findFileByUri currently returns null if
resolveWslLspLocalPath(fileUri) yields a path but
findLocalFileByPath(resolvedPath) returns null, preventing the superclass
fallback; change the logic in findFileByUri so that after resolving resolvedPath
you call findLocalFileByPath(resolvedPath) and only return it if non-null,
otherwise call and return super.findFileByUri(fileUri) (keep the
RuffLoggingService.log call for the resolved attempt or add a log on miss as
needed).

In `@src/com/koxudaxi/ruff/Ruff.kt`:
- Around line 722-732: The non-WSL branch of resolveStdinFileName omits the
final filePath fallback; change the return to use toWindowsWslUncPath(filePath)
?: projectRelativeFilePath ?: filePath so it mirrors the executableRunsInWsl
branch and always returns a filename when possible; update any affected tests
(or add a test) that cover the non-WSL case without projectRelativeFilePath.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dc7f0b6a-c878-4c35-9b4b-3f5630f22f51

📥 Commits

Reviewing files that changed from the base of the PR and between 092b694 and bc90035.

📒 Files selected for processing (7)
  • gradle/libs.versions.toml
  • src/com/koxudaxi/ruff/Ruff.kt
  • src/com/koxudaxi/ruff/lsp/intellij/RuffLspServerSupportProvider.kt
  • src/com/koxudaxi/ruff/lsp/lsp4ij/RuffLanguageClient.kt
  • src/com/koxudaxi/ruff/lsp/lsp4ij/RuffLanguageServerFactory.kt
  • testSrc/RuffLspServerSupportProviderTest.kt
  • testSrc/com/koxudaxi/ruff/RuffStdinFilenameArgsTest.kt
✅ Files skipped from review due to trivial changes (3)
  • src/com/koxudaxi/ruff/lsp/lsp4ij/RuffLanguageServerFactory.kt
  • gradle/libs.versions.toml
  • testSrc/com/koxudaxi/ruff/RuffStdinFilenameArgsTest.kt

Comment on lines +121 to +130
override fun findFileByUri(fileUri: String): VirtualFile? {
if (!SystemInfo.isWindows) {
return super.findFileByUri(fileUri)
}
val resolvedPath = resolveWslLspLocalPath(fileUri)
if (resolvedPath != null) {
RuffLoggingService.log(project, "LSP resolve URI: $fileUri -> $resolvedPath")
return findLocalFileByPath(resolvedPath)
}
return super.findFileByUri(fileUri)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Preserve the superclass fallback when WSL path lookup misses.

If resolveWslLspLocalPath(fileUri) succeeds but findLocalFileByPath(resolvedPath) returns null, we currently stop there and never try super.findFileByUri(fileUri). That turns this WSL mapping into a hard failure instead of a best-effort optimization.

💡 Suggested fix
 override fun findFileByUri(fileUri: String): VirtualFile? {
     if (!SystemInfo.isWindows) {
         return super.findFileByUri(fileUri)
     }
     val resolvedPath = resolveWslLspLocalPath(fileUri)
     if (resolvedPath != null) {
         RuffLoggingService.log(project, "LSP resolve URI: $fileUri -> $resolvedPath")
-        return findLocalFileByPath(resolvedPath)
+        findLocalFileByPath(resolvedPath)?.let { return it }
     }
     return super.findFileByUri(fileUri)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/com/koxudaxi/ruff/lsp/intellij/RuffLspServerSupportProvider.kt` around
lines 121 - 130, findFileByUri currently returns null if
resolveWslLspLocalPath(fileUri) yields a path but
findLocalFileByPath(resolvedPath) returns null, preventing the superclass
fallback; change the logic in findFileByUri so that after resolving resolvedPath
you call findLocalFileByPath(resolvedPath) and only return it if non-null,
otherwise call and return super.findFileByUri(fileUri) (keep the
RuffLoggingService.log call for the resolved attempt or add a log on miss as
needed).

Comment on lines +722 to +732
internal fun resolveStdinFileName(
filePath: String,
projectRelativeFilePath: String?,
executableRunsInWsl: Boolean,
wslPath: String?
): String? {
if (executableRunsInWsl) {
return wslPath ?: projectRelativeFilePath ?: filePath
}
return toWindowsWslUncPath(filePath) ?: projectRelativeFilePath
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there are tests covering the non-WSL case with file outside project
rg -n "resolveStdinFileName" --type=kotlin -A 10

Repository: koxudaxi/ruff-pycharm-plugin

Length of output: 5297


🏁 Script executed:

#!/bin/bash
# Read the complete test file
cat -n testSrc/com/koxudaxi/ruff/RuffStdinFilenameArgsTest.kt

Repository: koxudaxi/ruff-pycharm-plugin

Length of output: 2003


🏁 Script executed:

#!/bin/bash
# Find and examine the toWindowsWslUncPath function
rg -n "fun toWindowsWslUncPath" --type=kotlin -A 5

Repository: koxudaxi/ruff-pycharm-plugin

Length of output: 461


🏁 Script executed:

#!/bin/bash
# Check if there are any other test cases we might have missed
rg -n "fun \`" testSrc/com/koxudaxi/ruff/RuffStdinFilenameArgsTest.kt

Repository: koxudaxi/ruff-pycharm-plugin

Length of output: 352


Add fallback to filePath in non-WSL case for consistency.

The WSL executable branch (line 729) includes a final filePath fallback, while the non-WSL branch (line 731) does not. For files outside the project with a non-WSL executable, the function returns null, omitting --stdin-filename and losing filename context.

Suggest adding the same fallback for consistency:

Diff
-    return toWindowsWslUncPath(filePath) ?: projectRelativeFilePath
+    return toWindowsWslUncPath(filePath) ?: projectRelativeFilePath ?: filePath

This issue is not covered by existing tests (which all provide projectRelativeFilePath).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
internal fun resolveStdinFileName(
filePath: String,
projectRelativeFilePath: String?,
executableRunsInWsl: Boolean,
wslPath: String?
): String? {
if (executableRunsInWsl) {
return wslPath ?: projectRelativeFilePath ?: filePath
}
return toWindowsWslUncPath(filePath) ?: projectRelativeFilePath
}
internal fun resolveStdinFileName(
filePath: String,
projectRelativeFilePath: String?,
executableRunsInWsl: Boolean,
wslPath: String?
): String? {
if (executableRunsInWsl) {
return wslPath ?: projectRelativeFilePath ?: filePath
}
return toWindowsWslUncPath(filePath) ?: projectRelativeFilePath ?: filePath
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/com/koxudaxi/ruff/Ruff.kt` around lines 722 - 732, The non-WSL branch of
resolveStdinFileName omits the final filePath fallback; change the return to use
toWindowsWslUncPath(filePath) ?: projectRelativeFilePath ?: filePath so it
mirrors the executableRunsInWsl branch and always returns a filename when
possible; update any affected tests (or add a test) that cover the non-WSL case
without projectRelativeFilePath.

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.

Can't convert workspace URL to file path while opening WSL project locally

2 participants