дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
feat(remote): open WSL projects through a WSL transport (#989)
* perf(vfs): list directories off the main thread and reuse the repository lookup Opening a folder on a network share (\\wsl.localhost\<distro>\...) is slow because every listing ran on the main thread and walked up to the repository root and ran a repository discovery again. - fs_read_dir runs on the blocking pool. - GitignoreStack::cached keeps the repository root and the repo-level rules for a few seconds. An edit of .git/info/exclude is still seen at once (its mtime and size are checked) and every .gitignore / .ignore is read fresh. Refs #916 * feat(remote): open WSL projects through a WSL transport A folder of a WSL distro (\\wsl.localhost\<distro>\... or \\wsl$\...) now opens as a remote project whose openidea-server runs inside the distro, so the terminal, SDK discovery, run, language servers and git are native Linux. - RemoteTarget (ssh | wsl) replaces the SSH configuration id in the remote launch, connection, session and recents; old recents are read as SSH and a WSL target never inherits the legacy trust migration. - remote_wsl: probe the distro, stream the server into ~/.openidea/server and start it with `wsl.exe -d <distro> --exec <bin> --stdio`. Distro names are validated and every command goes through --exec with positional arguments. - wsl_distros / wsl_resolve commands; File > Open, the CLI and recents route WSL folders to the distro and focus the window that already has them. - Remote Development dialog: SSH/WSL choice with the detected distros; the status bar shows `WSL: <distro>` / `SSH: <host>`. - ide-jsonrpc: skip the NUL left at the start of a line by UTF-16 output of wsl.exe, which made the first message of the server disappear. Not run on Windows yet: only a stand-in for wsl.exe is tested. Refs #925, #327, #721 * fix(remote): satisfy clippy in WSL output decoding * style(run): format run types * fix: regenerate bindings and drop untranslated WSL catalog entry * test(i18n): list WSL as an untranslated product name * fix(run): pick build wrappers and shell quoting by the execution host A remote window runs commands on its Linux or macOS server, so a Windows desktop no longer selects gradlew.bat, mvnw.cmd or Windows quoting for it. * fix(welcome): open legacy WSL UNC recents and sessions through the distro Recents and the restored session called the local openProject for a \\wsl.localhost folder saved before WSL projects, which fails with OS error 123. They now resolve it like the other entry points. * fix(windows): keep WSL files out of remote project launches path_launch tested the WSL share before is_dir, so a first-instance CLI launch with a WSL file opened a remote project instead of LightEdit. * fix(remote): run-command OS comes from the remote host, not an assumption The server's OS was dropped after connect, so a restored session had none and isWindows() assumed every remote host is Unix. Keep it on the connection and the session, and resolve it through hostOs(). --------- Co-authored-by: NguyenBang <[email protected]> Co-authored-by: NguyenBang <[email protected]> Co-authored-by: l0ng-ai <[email protected]>
Цей коміт міститься в:
47 files changed
+1512
-220
No files matched your search
@@ -6,8 +6,9 @@ import { useRemoteProgress } from "@/platform/remote";
|
||||
import { ConnectHost } from "./ConnectDialog";
|
||||
import { closeConnectDialog, openConnectDialog, useConnectDialog } from "./model";
|
||||
|
||||
const { list, globalSettings, openRemote, executeAction } = vi.hoisted(() => ({
|
||||
const { list, distros, globalSettings, openRemote, executeAction } = vi.hoisted(() => ({
|
||||
list: vi.fn(),
|
||||
distros: vi.fn(),
|
||||
globalSettings: vi.fn(),
|
||||
openRemote: vi.fn(),
|
||||
executeAction: vi.fn(),
|
||||
@@ -15,7 +16,10 @@ const { list, globalSettings, openRemote, executeAction } = vi.hoisted(() => ({
|
||||
|
||||
vi.mock("@/platform/ipc", async (importOriginal) => {
|
||||
const real = await importOriginal<typeof import("@/platform/ipc")>();
|
||||
return { ...real, commands: { ...real.commands, remoteBootstrapList: list, settingsGetGlobal: globalSettings } };
|
||||
return {
|
||||
...real,
|
||||
commands: { ...real.commands, remoteBootstrapList: list, wslDistros: distros, settingsGetGlobal: globalSettings },
|
||||
};
|
||||
});
|
||||
vi.mock("@/workbench/projectWindow", () => ({ openRemoteProject: openRemote }));
|
||||
vi.mock("@/platform/actions", async (importOriginal) => ({
|
||||
@@ -35,6 +39,7 @@ const entry = (path: string, isDir = true): RemoteEntry => ({
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
distros.mockResolvedValue({ status: "ok", data: [] });
|
||||
// A remote project's window: its settings are the remote host's, without this machine's SSH configurations.
|
||||
useProject.setState({ project: null, opening: false, settings: { ssh: { configs: [] } } as unknown as Settings });
|
||||
globalSettings.mockResolvedValue({
|
||||
@@ -87,7 +92,9 @@ it("browses the selected SSH host, navigates folders and opens one without a cur
|
||||
await waitFor(() => expect(screen.getByLabelText("Project directory:")).toHaveValue("/home/dev/projects"));
|
||||
expect(within(folders).getAllByRole("button")).toHaveLength(1);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Open" }));
|
||||
await waitFor(() => expect(openRemote).toHaveBeenCalledWith("selected", "/home/dev/projects"));
|
||||
await waitFor(() =>
|
||||
expect(openRemote).toHaveBeenCalledWith({ kind: "ssh", config: "selected" }, "/home/dev/projects"),
|
||||
);
|
||||
expect(useConnectDialog.getState().open).toBe(false);
|
||||
expect(useProject.getState().project).toBeNull();
|
||||
});
|
||||
@@ -125,3 +132,24 @@ it("offers SSH configuration and disables Browse until one exists, then lists th
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "Browse" })).toBeEnabled());
|
||||
expect(screen.getByRole("button", { name: "SSH configuration" })).toHaveTextContent("dev");
|
||||
});
|
||||
|
||||
it("hides the WSL choice when no distribution is installed", async () => {
|
||||
render(<ConnectHost />);
|
||||
await waitFor(() => expect(distros).toHaveBeenCalled());
|
||||
expect(screen.queryByRole("button", { name: "Connect through" })).toBeNull();
|
||||
});
|
||||
|
||||
it("opens a folder of the chosen WSL distribution, without browsing over SFTP", async () => {
|
||||
const installed = ["Ubuntu-24.04", "Debian"];
|
||||
distros.mockResolvedValue({ status: "ok", data: installed });
|
||||
openRemote.mockResolvedValue(true);
|
||||
render(<ConnectHost />);
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Connect through" }));
|
||||
fireEvent.click(screen.getByRole("option", { name: "WSL" }));
|
||||
expect(screen.queryByRole("button", { name: "Browse" })).toBeNull();
|
||||
fireEvent.click(screen.getByRole("button", { name: "WSL distribution" }));
|
||||
fireEvent.click(screen.getByRole("option", { name: installed[1] }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Open" }));
|
||||
await waitFor(() => expect(openRemote).toHaveBeenCalledWith({ kind: "wsl", distro: installed[1] }, "~"));
|
||||
expect(list).not.toHaveBeenCalled();
|
||||
});
|
||||
Reference in new issue
Заблокувати користувача