дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
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
Цей коміт міститься в:
1 джерело
6b89984863
коміт
3e88fa1f39
6 files changed
+32
-12
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
Reference in new issue
Заблокувати користувача