diff --git a/crates/ide-host/src/ops/routes.rs b/crates/ide-host/src/ops/routes.rs index cbf2965e0..3d6f2e1ce 100644 --- a/crates/ide-host/src/ops/routes.rs +++ b/crates/ide-host/src/ops/routes.rs @@ -20,7 +20,7 @@ const MAX_FILE_BYTES: u64 = 4 << 20; #[derive(Clone, Debug, Serialize, Deserialize, Type)] #[serde(rename_all = "camelCase")] pub struct RouteSource { - pub path: String, + pub path: ide_core::IpcPath, pub text: String, } @@ -51,7 +51,7 @@ pub(crate) fn sources_in(root: &Path, excludes: &[String]) -> Vec { return; } bytes.fetch_add(text.len(), Ordering::Relaxed); - found.push(RouteSource { path: path.to_string_lossy().into_owned(), text }); + found.push(RouteSource { path: path.to_path_buf().into(), text }); }); let mut files = found.into_inner().unwrap_or_default(); files.sort_by(|a, b| a.path.cmp(&b.path)); @@ -116,7 +116,7 @@ mod tests { std::fs::write(tmp.path().join("src/Notes.txt"), "@GetMapping(\"/a\")").unwrap(); let files = sources_in(tmp.path(), &[]); assert_eq!(files.len(), 1, "{:?}", files.iter().map(|f| &f.path).collect::>()); - assert!(Path::new(&files[0].path).ends_with("src/Ctl.java"), "{}", files[0].path); + assert!(files[0].path.ends_with("src/Ctl.java"), "{:?}", files[0].path); assert!(files[0].text.contains("@GetMapping")); } @@ -129,6 +129,6 @@ mod tests { std::fs::write(tmp.path().join("build/B.java"), "@RequestMapping(\"/b\")").unwrap(); let files = sources_in(tmp.path(), &["build/**".to_owned()]); assert_eq!(files.len(), 1, "{:?}", files.iter().map(|f| &f.path).collect::>()); - assert!(Path::new(&files[0].path).ends_with("module/src/A.java"), "{}", files[0].path); + assert!(files[0].path.ends_with("module/src/A.java"), "{:?}", files[0].path); } } diff --git a/crates/ide-host/src/ops/vcs/commands.rs b/crates/ide-host/src/ops/vcs/commands.rs index 4075dd9dc..d619c0a01 100644 --- a/crates/ide-host/src/ops/vcs/commands.rs +++ b/crates/ide-host/src/ops/vcs/commands.rs @@ -492,7 +492,7 @@ pub async fn vcs_add_to_ignore( window: &str, paths: Vec, destination: IgnoreDestination, -) -> IdeResult> { +) -> IdeResult> { host.require_trust(window)?; let project = host.project(window)?; blocking(move || { @@ -509,7 +509,7 @@ pub async fn vcs_add_to_ignore( watcher.reload_ignore_rules(); } project.sink.emit(ide_core::IdeEvent::VcsChanged { root: project.root.clone().into() }); - result.map(|files| files.into_iter().map(|p| p.to_string_lossy().into_owned()).collect()) + result.map(|files| files.into_iter().map(Into::into).collect()) }) .await } diff --git a/src-tauri/src/commands/vcs.rs b/src-tauri/src/commands/vcs.rs index 934398467..8db05d083 100644 --- a/src-tauri/src/commands/vcs.rs +++ b/src-tauri/src/commands/vcs.rs @@ -221,7 +221,7 @@ pub async fn vcs_add_to_ignore( state: State<'_, AppState>, paths: Vec, destination: IgnoreDestination, -) -> IdeResult> { +) -> IdeResult> { ide_host::ops::vcs::vcs_add_to_ignore(&state.host, window.label(), paths, destination).await } diff --git a/src/bindings.ts b/src/bindings.ts index 1b10a6f03..3547cdce7 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -491,7 +491,7 @@ export const commands = { vcsBranches: (repo: string | null) => typedError(__TAURI_INVOKE("vcs_branches", { repo })), vcsStashes: (repo: string | null) => typedError(__TAURI_INVOKE("vcs_stashes", { repo })), vcsStage: (paths: string[]) => typedError(__TAURI_INVOKE("vcs_stage", { paths })), - vcsAddToIgnore: (paths: string[], destination: IgnoreDestination) => typedError(__TAURI_INVOKE("vcs_add_to_ignore", { paths, destination })), + vcsAddToIgnore: (paths: string[], destination: IgnoreDestination) => typedError(__TAURI_INVOKE("vcs_add_to_ignore", { paths, destination })), vcsUnstage: (paths: string[]) => typedError(__TAURI_INVOKE("vcs_unstage", { paths })), vcsStagePatch: (patch: string) => typedError(__TAURI_INVOKE("vcs_stage_patch", { patch })), vcsDiscard: (paths: string[]) => typedError(__TAURI_INVOKE("vcs_discard", { paths })), @@ -6199,7 +6199,7 @@ export type Risk = export type RootKind = "source" | "test" | "resources" | "testResources" | "excluded"; export type RouteSource = { - path: string, + path: IpcPath, text: string, }; diff --git a/src/features/http-client/spring/routeIndex.test.ts b/src/features/http-client/spring/routeIndex.test.ts index 6299973a5..28521dda4 100644 --- a/src/features/http-client/spring/routeIndex.test.ts +++ b/src/features/http-client/spring/routeIndex.test.ts @@ -61,6 +61,23 @@ describe("the Spring endpoint provider", () => { expect(scanSources).toHaveBeenCalledTimes(2); }); + it("matches sources by path segments, whatever separator the host used", async () => { + scanSources.mockResolvedValue({ + status: "ok", + data: [ + { ...source(), path: "D:\\work\\demo\\src\\DeepLinkController.java" }, + { ...source("other"), path: "D:/work/demo2/DeepLinkController.java" }, + ], + }); + expect(await routeIndex("D:/work/demo")).toMatchObject([ + { + methodName: "searchMenus", + path: "D:/work/demo/src/DeepLinkController.java", + rel: "src/DeepLinkController.java", + }, + ]); + }); + it.each(["created", "modified", "removed"] as const)("invalidates on %s mapping sources", async (kind) => { scanSources.mockResolvedValue({ status: "ok", data: [source()] }); dispose = registerSpringEndpointProvider(); diff --git a/src/features/http-client/spring/routeIndex.ts b/src/features/http-client/spring/routeIndex.ts index edbb84048..2388a828c 100644 --- a/src/features/http-client/spring/routeIndex.ts +++ b/src/features/http-client/spring/routeIndex.ts @@ -1,5 +1,6 @@ /** Spring request-path index, cached per project until a mapping source changes. */ import { call, commands, onIdeEvent } from "@/platform/ipc"; +import { ipcPath, isSameOrInside } from "@/platform/paths"; import { registerEndpointProvider, type EndpointEntry } from "@/workbench/contributions"; import { springControllers } from "./endpoints"; @@ -40,8 +41,10 @@ export function routeIndex(root: string | null): Promise { async function scan(root: string): Promise { const sources = await call(commands.springRouteSources()); const routes: EndpointEntry[] = []; + const base = ipcPath(root).replace(/\/$/, ""); for (const source of sources) { - if (!source.path.startsWith(root)) continue; + const path = ipcPath(source.path); + if (!isSameOrInside(path, base)) continue; for (const controller of springControllers(source.text)) { for (const endpoint of controller.endpoints) { const at = lineCol(source.text, endpoint.offset); @@ -53,8 +56,8 @@ async function scan(root: string): Promise { url, controller: controller.name, methodName: endpoint.methodName, - path: source.path, - rel: source.path.slice(root.length + 1), + path, + rel: path.slice(base.length + 1), line: at.line, col: at.col, });