mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-12 14:15:14 +00:00
JetBrainsPluginClient: Generalise path matching
* Account for additional prefixes * Account for WSL vs. Windows path mismatch Add a test for path matching
This commit is contained in:
1 parent
448b3693bc
commit
4a0812972f
2 files changed
+67
-19
No files matched your search
@@ -118,43 +118,71 @@ class JetBrainsPluginClient(ToStringMixin):
|
||||
if client.matches(resolved_path):
|
||||
return client
|
||||
|
||||
plugin_paths_found = []
|
||||
for port in range(cls.BASE_PORT, cls.BASE_PORT + 20):
|
||||
client = JetBrainsPluginClient(port)
|
||||
if client.matches(resolved_path):
|
||||
log.info("Found matching %s", client)
|
||||
cls._last_port = port
|
||||
return client
|
||||
elif client._project_root is not None:
|
||||
plugin_paths_found.append(client._project_root)
|
||||
|
||||
log.warning(
|
||||
"Searched for Serena JetBrains plugin service for project at %s but found no matching service. "
|
||||
"Found plugin instances for the following project paths: %s",
|
||||
resolved_path,
|
||||
plugin_paths_found,
|
||||
)
|
||||
raise ServerNotFoundError("Found no Serena service in a JetBrains IDE instance for the project at " + str(resolved_path))
|
||||
|
||||
@staticmethod
|
||||
def _normalize_wsl_path(path_str: str) -> Path:
|
||||
def _paths_match(resolved_serena_path: str, plugin_path: str) -> bool:
|
||||
"""
|
||||
Normalize WSL UNC paths to Linux paths for comparison.
|
||||
Checks whether the resolved Serena path matches the plugin path, accounting for possible prefixes
|
||||
in the plugin path, different file system perspectives, and case sensitivity.
|
||||
|
||||
When JetBrains IDE runs on Windows with a project opened from WSL,
|
||||
it returns paths like `//wsl.localhost/Ubuntu-24.04/home/user/project`
|
||||
or `//wsl$/Ubuntu/home/user/project`. This method converts such paths
|
||||
to standard Linux format `/home/user/project` for proper matching.
|
||||
Concrete aspects considered:
|
||||
- The plugin path may contain prefixes:
|
||||
- The plugin path may be a WSL UNC path, e.g. `//wsl.localhost/Ubuntu-24.04/home/user/project`
|
||||
or `//wsl$/Ubuntu/home/user/project` while Serena will just have `/home/user/project`
|
||||
- Other prefixes like `/workspaces/serena/C:/Users/user/projects/my-app`
|
||||
- One path may use a different file system perspective (particularly WSL vs Windows-native) but still
|
||||
point to the same location, e.g. `/mnt/c/` vs `C:/`
|
||||
- Case sensitivity
|
||||
|
||||
:param path_str: Path string that may be a WSL UNC path
|
||||
:return: Normalized Path object
|
||||
:param resolved_serena_path: The resolved project root path from Serena's perspective
|
||||
:param plugin_path: The project root path reported by the plugin (which may be a WSL UNC path)
|
||||
:return: True if the paths match, False otherwise
|
||||
"""
|
||||
path_str = str(path_str)
|
||||
# Match //wsl.localhost/<distro>/... or //wsl$/<distro>/...
|
||||
match = re.match(r"^//wsl(?:\.localhost|\$)/[^/]+(.*)$", path_str, re.IGNORECASE)
|
||||
if match:
|
||||
return Path(match.group(1))
|
||||
return Path(path_str)
|
||||
# try to resolve the plugin path, checking for a direct match
|
||||
# (this is robust against symlinks as long as there are no prefixes)
|
||||
try:
|
||||
resolved_plugin_path = str(Path(plugin_path).resolve())
|
||||
if resolved_plugin_path == resolved_serena_path:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
def normalise_wsl_mnt(path_str: str) -> str:
|
||||
# normalise WSL /mnt/c/ to c:/ for comparison
|
||||
return re.sub(r"/mnt/([a-z])/", r"\1:/", path_str, flags=re.IGNORECASE)
|
||||
|
||||
# standardise paths for comparison: normalise WSL /mnt/ to Windows paths and ignore case
|
||||
std_serena_path = normalise_wsl_mnt(str(resolved_serena_path)).lower()
|
||||
std_plugin_path = normalise_wsl_mnt(str(plugin_path)).lower()
|
||||
|
||||
# At this point, the plugin path may still contain prefixes, so we check if the Serena path is a suffix of the plugin path
|
||||
return std_plugin_path.endswith(std_serena_path)
|
||||
|
||||
def matches(self, resolved_path: Path) -> bool:
|
||||
"""
|
||||
:param resolved_path: the resolved project root path from Serena's perspective
|
||||
:return: whether this client instance matches the given project path
|
||||
"""
|
||||
if self._project_root is None:
|
||||
return False
|
||||
try:
|
||||
plugin_root = self._normalize_wsl_path(self._project_root)
|
||||
return plugin_root.resolve() == resolved_path
|
||||
except ConnectionError:
|
||||
return False
|
||||
return self._paths_match(str(resolved_path), self._project_root)
|
||||
|
||||
def is_version_at_least(self, *version_parts: int) -> bool:
|
||||
if self._plugin_version is None:
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import pytest
|
||||
|
||||
from serena.constants import REPO_ROOT
|
||||
from serena.jetbrains.jetbrains_plugin_client import JetBrainsPluginClient
|
||||
|
||||
|
||||
class TestSerenaJetBrainsPluginClient:
|
||||
@pytest.mark.parametrize(
|
||||
"serena_path, plugin_path",
|
||||
[
|
||||
(REPO_ROOT, REPO_ROOT),
|
||||
("/home/user/project", "/home/user/project"),
|
||||
("/home/user/project", "//wsl.localhost/Ubuntu-24.04/home/user/project"),
|
||||
("/home/user/project", "//wsl$/Ubuntu/home/user/project"),
|
||||
("/home/user/project", "//wsl$/Ubuntu/home/user/project"),
|
||||
("/mnt/c/Users/user/projects/my-app", "/workspaces/serena/C:/Users/user/projects/my-app"),
|
||||
],
|
||||
)
|
||||
def test_path_matching(self, serena_path, plugin_path) -> None:
|
||||
assert JetBrainsPluginClient._paths_match(serena_path, plugin_path)
|
||||
Reference in new issue
Block a user