дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
* 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]>
84 рядки
3.3 KiB
TypeScript
84 рядки
3.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { commands } from "@/platform/ipc";
|
|
import { useRemote } from "@/platform/remote";
|
|
import { invokeLocal } from "@/platform/tauriCore";
|
|
import { useTasks } from "@/workbench/tasks";
|
|
import { beginLocalImport, tauriFs } from "./fs";
|
|
|
|
vi.mock("@/platform/tauriCore", async (original) => ({
|
|
...(await original<typeof import("@/platform/tauriCore")>()),
|
|
invokeLocal: vi.fn(async () => undefined),
|
|
}));
|
|
|
|
beforeEach(() => {
|
|
useTasks.setState({ tasks: [] });
|
|
useRemote.setState({
|
|
session: {
|
|
target: { kind: "ssh", config: "s" },
|
|
root: "/remote",
|
|
host: "host",
|
|
os: "linux",
|
|
commands: ["fs_copy", "cancel_request"],
|
|
},
|
|
});
|
|
});
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
useRemote.setState({ session: null });
|
|
useTasks.setState({ tasks: [] });
|
|
});
|
|
|
|
describe("client file routing", () => {
|
|
it("uploads OS files locally and cancels the client request rather than a remote one", async () => {
|
|
let finish!: (r: { status: "ok"; data: string[] }) => void;
|
|
const upload = vi.spyOn(commands, "remoteImportLocal").mockReturnValue(
|
|
new Promise((resolve) => {
|
|
finish = resolve;
|
|
}),
|
|
);
|
|
const copy = vi.spyOn(commands, "fsCopy");
|
|
const work = tauriFs.importLocal("/client/a", "/remote/a");
|
|
const task = useTasks.getState().tasks[0]!;
|
|
const request = upload.mock.calls[0]![2];
|
|
task.cancel!();
|
|
expect(invokeLocal).toHaveBeenCalledWith("cancel_request", { id: request });
|
|
finish({ status: "ok", data: [] });
|
|
await work;
|
|
expect(upload).toHaveBeenCalledWith("/client/a", "/remote/a", request);
|
|
expect(copy).not.toHaveBeenCalled();
|
|
expect(useTasks.getState().tasks[0]?.done).toBe(true);
|
|
});
|
|
|
|
it("surfaces a disconnected upload and finishes its task", async () => {
|
|
vi.spyOn(commands, "remoteImportLocal").mockResolvedValue({
|
|
status: "error",
|
|
error: { kind: "unavailable", message: "Remote project is disconnected" },
|
|
});
|
|
await expect(tauriFs.importLocal("/client/a", "/remote/a")).rejects.toThrow("Remote project is disconnected");
|
|
expect(useTasks.getState().tasks[0]?.done).toBe(true);
|
|
});
|
|
|
|
it("shares one task across paths and prevents starting another after Cancel", async () => {
|
|
const upload = vi.spyOn(commands, "remoteImportLocal").mockResolvedValue({ status: "ok", data: [] });
|
|
const batch = beginLocalImport();
|
|
await tauriFs.importLocal("/client/a", "/remote/a", batch);
|
|
await tauriFs.importLocal("/client/b", "/remote/b", batch);
|
|
expect(useTasks.getState().tasks).toHaveLength(1);
|
|
expect(useTasks.getState().tasks[0]?.done).toBe(false);
|
|
useTasks.getState().tasks[0]!.cancel!();
|
|
await expect(tauriFs.importLocal("/client/c", "/remote/c", batch)).rejects.toMatchObject({ kind: "cancelled" });
|
|
expect(upload).toHaveBeenCalledTimes(2);
|
|
batch.finish();
|
|
expect(useTasks.getState().tasks[0]?.done).toBe(true);
|
|
});
|
|
|
|
it("uses project copy for a local destination", async () => {
|
|
useRemote.setState({ session: null });
|
|
const copy = vi.spyOn(commands, "fsCopy").mockResolvedValue({ status: "ok", data: null });
|
|
const upload = vi.spyOn(commands, "remoteImportLocal");
|
|
await tauriFs.importLocal("/client/a", "/project/a");
|
|
expect(copy).toHaveBeenCalledWith("/client/a", "/project/a");
|
|
expect(upload).not.toHaveBeenCalled();
|
|
});
|
|
});
|