fix(http-client): list Spring endpoints on Windows (#1456)

Spring route sources came back from the host with native separators, so on Windows every source failed the prefix check against the forward-slash project root and Search Everywhere showed no endpoints. RouteSource.path is now an IpcPath and the index compares by path segment; the add-to-ignore result also returns IPC paths.

Fixes #1422
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-05 12:30:25 +08:00
1 джерело 6b89984863
коміт 3e88fa1f39
6 files changed
+32 -12

No files matched your search

+4 -4
Переглянути файл
@@ -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<RouteSource> {
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::<Vec<_>>());
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::<Vec<_>>());
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);
}
}
+2 -2
Переглянути файл
@@ -492,7 +492,7 @@ pub async fn vcs_add_to_ignore(
window: &str,
paths: Vec<PathBuf>,
destination: IgnoreDestination,
) -> IdeResult<Vec<String>> {
) -> IdeResult<Vec<ide_core::IpcPath>> {
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
}
+1 -1
Переглянути файл
@@ -221,7 +221,7 @@ pub async fn vcs_add_to_ignore(
state: State<'_, AppState>,
paths: Vec<PathBuf>,
destination: IgnoreDestination,
) -> IdeResult<Vec<String>> {
) -> IdeResult<Vec<ide_core::IpcPath>> {
ide_host::ops::vcs::vcs_add_to_ignore(&state.host, window.label(), paths, destination).await
}
+2 -2
Переглянути файл
@@ -491,7 +491,7 @@ export const commands = {
vcsBranches: (repo: string | null) => typedError<Branch[], IdeError>(__TAURI_INVOKE("vcs_branches", { repo })),
vcsStashes: (repo: string | null) => typedError<Stash[], IdeError>(__TAURI_INVOKE("vcs_stashes", { repo })),
vcsStage: (paths: string[]) => typedError<null, IdeError>(__TAURI_INVOKE("vcs_stage", { paths })),
vcsAddToIgnore: (paths: string[], destination: IgnoreDestination) => typedError<string[], IdeError>(__TAURI_INVOKE("vcs_add_to_ignore", { paths, destination })),
vcsAddToIgnore: (paths: string[], destination: IgnoreDestination) => typedError<IpcPath[], IdeError>(__TAURI_INVOKE("vcs_add_to_ignore", { paths, destination })),
vcsUnstage: (paths: string[]) => typedError<null, IdeError>(__TAURI_INVOKE("vcs_unstage", { paths })),
vcsStagePatch: (patch: string) => typedError<null, IdeError>(__TAURI_INVOKE("vcs_stage_patch", { patch })),
vcsDiscard: (paths: string[]) => typedError<null, IdeError>(__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,
};
+17
Переглянути файл
@@ -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();
+6 -3
Переглянути файл
@@ -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<EndpointEntry[]> {
async function scan(root: string): Promise<EndpointEntry[]> {
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<EndpointEntry[]> {
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,
});