mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-13 04:34:21 -10:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1ae3ca91d | ||
|
|
11b42041d3 | ||
|
|
4c811ba4f5 | ||
|
|
573f98627e |
No files matched your search
@@ -393,11 +393,13 @@ fn scan_versions(dir: &Path, typed: &str, budget: &mut u32) -> Vec<PomCompletion
|
||||
out
|
||||
}
|
||||
|
||||
/// The repository directory of the `/`-separated `coordinates` (a group id, or
|
||||
/// a group and an artifact id), each segment validated against the layout's
|
||||
/// alphabet so POM text cannot walk outside `repo`.
|
||||
/// The repository directory of `coordinates` (a group id, or a group and an artifact
|
||||
/// id): the group's dots are directories, an artifact id is one directory whatever dots
|
||||
/// it has (`org/eclipse/platform/org.eclipse.core.runtime`). Each segment is validated
|
||||
/// against the layout's alphabet so POM text cannot walk outside `repo`.
|
||||
fn repo_dir(repo: &Path, coordinates: &[&str]) -> Option<PathBuf> {
|
||||
let segments: Vec<&str> = coordinates.iter().flat_map(|c| c.split('.')).collect();
|
||||
let (group, artifact) = coordinates.split_first()?;
|
||||
let segments: Vec<&str> = group.split('.').chain(artifact.iter().copied()).collect();
|
||||
segments.iter().all(|s| segment(s)).then(|| segments.iter().fold(repo.to_path_buf(), |p, s| p.join(s)))
|
||||
}
|
||||
|
||||
@@ -460,6 +462,7 @@ mod tests {
|
||||
"org/example/lib/2.0-beta-1/lib-2.0-beta-1.jar",
|
||||
"org/example/lib/2.0/lib-2.0.pom",
|
||||
"net/nu/netty/4.1.100/netty-4.1.100.jar",
|
||||
"org/eclipse/platform/org.eclipse.core.runtime/3.31.0/org.eclipse.core.runtime-3.31.0.jar",
|
||||
// Failed downloads (a version typed while editing): tracking files only, not installed.
|
||||
"org/example/lib/2/lib-2.jar.lastUpdated",
|
||||
"org/example/lib/3.0/lib-3.0.pom.lastUpdated",
|
||||
@@ -570,9 +573,10 @@ mod tests {
|
||||
};
|
||||
// Only directories holding artifacts are group ids: `net` and `org`
|
||||
// alone never receive an install, their deeper levels do.
|
||||
assert_eq!(options(""), ["net.nu", "org.apache.maven", "org.apache.maven.plugins", "org.example"]);
|
||||
assert_eq!(options("org"), ["org.apache.maven", "org.apache.maven.plugins", "org.example"]);
|
||||
assert_eq!(options("org."), ["org.apache.maven", "org.apache.maven.plugins", "org.example"]);
|
||||
let org = ["org.apache.maven", "org.apache.maven.plugins", "org.eclipse.platform", "org.example"];
|
||||
assert_eq!(options(""), [&["net.nu"][..], &org].concat());
|
||||
assert_eq!(options("org"), org);
|
||||
assert_eq!(options("org."), org);
|
||||
assert_eq!(options("org.spr"), Vec::<String>::new());
|
||||
assert_eq!(options("net."), ["net.nu"]);
|
||||
}
|
||||
@@ -623,6 +627,19 @@ mod tests {
|
||||
};
|
||||
assert_eq!(options(""), ["2.0", "2.0-beta-1", "1.10", "1.9.1", "1.0"]);
|
||||
assert_eq!(options("1."), ["1.10", "1.9.1", "1.0"]);
|
||||
// An artifact id with dots is one directory.
|
||||
let dotted = local_options(
|
||||
&repo,
|
||||
&PomCompletionTarget {
|
||||
from: 0,
|
||||
to: 0,
|
||||
typed: "".into(),
|
||||
kind: PomCompletionKind::Version,
|
||||
group: Some("org.eclipse.platform".into()),
|
||||
artifact: Some("org.eclipse.core.runtime".into()),
|
||||
},
|
||||
);
|
||||
assert_eq!(dotted.into_iter().map(|o| o.label).collect::<Vec<_>>(), ["3.31.0"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -61,13 +61,16 @@ pub async fn sdk_python_project_interpreters(host: &Host, window: &str) -> IdeRe
|
||||
host.require_trust(window)?;
|
||||
let current = host.project(window).ok().map(|p| p.root.clone());
|
||||
let config = host.config_dir.clone();
|
||||
// Probing runs the project's interpreter (and its settings may name any program): only
|
||||
// projects trusted in their own right, not ones last opened in Restricted Mode.
|
||||
let roots: Vec<PathBuf> = ide_workspace::RecentProjects::load(&config)
|
||||
.list()
|
||||
.into_iter()
|
||||
.map(|p| p.path.into_path_buf())
|
||||
.filter(|root| current.as_deref() != Some(root.as_path()))
|
||||
.filter(|root| host.trust.window_trusted(root).unwrap_or(false))
|
||||
.collect();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let roots: Vec<PathBuf> = ide_workspace::RecentProjects::load(&config)
|
||||
.list()
|
||||
.into_iter()
|
||||
.map(|p| p.path.into_path_buf())
|
||||
.filter(|root| current.as_deref() != Some(root.as_path()))
|
||||
.collect();
|
||||
// Each project resolves its configured interpreter, else its own virtualenv. The
|
||||
// probes run `python`, so keep them off the async runtime and bound how many run at
|
||||
// once: the recent list can hold ~30 projects, and each probe starts a process.
|
||||
@@ -521,6 +524,10 @@ mod tests {
|
||||
host.trust.decide(&root, true, false).unwrap();
|
||||
host.open_project("main", &root).unwrap();
|
||||
|
||||
// A project last opened in Restricted Mode: its interpreter is never run.
|
||||
host.trust.decide(&other, false, false).unwrap();
|
||||
assert!(sdk_python_project_interpreters(&host, "main").await.unwrap().is_empty());
|
||||
host.trust.decide(&other, true, false).unwrap();
|
||||
let list = sdk_python_project_interpreters(&host, "main").await.unwrap();
|
||||
assert_eq!(list.len(), 1, "{list:?}");
|
||||
assert_eq!(&*list[0].project, other.as_path());
|
||||
|
||||
@@ -239,6 +239,9 @@ impl LspManager {
|
||||
cancel: &CancellationToken,
|
||||
) -> IdeResult<DocumentCheck> {
|
||||
let snap = self.snapshot(doc)?;
|
||||
// The text may have changed before this call synced it (the file watcher reloaded it):
|
||||
// only diagnostics published after the servers got the current text are fresh.
|
||||
let changed = Some(changed.map_or(snap.changed, |c| c.max(snap.changed)));
|
||||
let deadline = tokio::time::Instant::now() + timeout;
|
||||
let left = || deadline.saturating_duration_since(tokio::time::Instant::now());
|
||||
let ready = tokio::select! {
|
||||
|
||||
@@ -120,6 +120,8 @@ struct DocState {
|
||||
compose_detected: bool,
|
||||
/// The notebook a code cell belongs to: synced through `notebookDocument/*`, not as a file.
|
||||
notebook: Option<PathBuf>,
|
||||
/// When the servers last got new text (open or change): diagnostics published before are stale.
|
||||
changed: std::time::Instant,
|
||||
}
|
||||
|
||||
impl DocState {
|
||||
@@ -158,6 +160,8 @@ struct DocSnap {
|
||||
language: LanguageId,
|
||||
text: Arc<DocText>,
|
||||
servers: Vec<Arc<Server>>,
|
||||
/// [`DocState::changed`].
|
||||
changed: std::time::Instant,
|
||||
}
|
||||
|
||||
impl DocSnap {
|
||||
@@ -1170,6 +1174,7 @@ impl LspManager {
|
||||
servers: servers.iter().map(|s| Attached { server: s.clone(), opened_gen: None }).collect(),
|
||||
compose_detected,
|
||||
notebook: None,
|
||||
changed: std::time::Instant::now(),
|
||||
};
|
||||
let compose_added = {
|
||||
let mut docs = self.docs.lock();
|
||||
@@ -1217,6 +1222,7 @@ impl LspManager {
|
||||
let mut docs = self.docs.lock();
|
||||
let Some(d) = docs.get_mut(&doc) else { return };
|
||||
d.version = version as i32;
|
||||
d.changed = std::time::Instant::now();
|
||||
|
||||
// Servers that have this document open on their current instance.
|
||||
let targets: Vec<(Arc<Server>, Running, SyncKind, Encoding)> = d
|
||||
@@ -1392,6 +1398,7 @@ impl LspManager {
|
||||
language: d.language.clone(),
|
||||
text: d.text.clone(),
|
||||
servers,
|
||||
changed: d.changed,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ impl LspManager {
|
||||
{
|
||||
if d.text.as_str() != cell.source {
|
||||
Arc::make_mut(&mut d.text).set(&cell.source);
|
||||
d.changed = std::time::Instant::now();
|
||||
}
|
||||
d.version += 1;
|
||||
state.cells.push((cell.id.clone(), doc));
|
||||
@@ -234,6 +235,7 @@ impl LspManager {
|
||||
servers: state.servers.iter().map(|s| Attached { server: s.clone(), opened_gen: None }).collect(),
|
||||
compose_detected: false,
|
||||
notebook: Some(path.to_owned()),
|
||||
changed: std::time::Instant::now(),
|
||||
},
|
||||
);
|
||||
state.cells.push((cell.id.clone(), doc));
|
||||
@@ -259,6 +261,7 @@ impl LspManager {
|
||||
}
|
||||
Arc::make_mut(&mut d.text).set(&cell.source);
|
||||
d.version += 1;
|
||||
d.changed = std::time::Instant::now();
|
||||
content.push(json!({
|
||||
"document": { "uri": d.uri, "version": d.version },
|
||||
"changes": [{ "text": cell.source }],
|
||||
|
||||
@@ -2204,6 +2204,12 @@ async fn checking_a_document_saves_it_and_waits_for_fresh_diagnostics() {
|
||||
let stale = h.mgr.check_document(doc, changed, Duration::from_millis(800), &cancel).await.unwrap();
|
||||
assert!(stale.servers[0].checked && stale.servers[0].incomplete, "{:?}", stale.servers);
|
||||
assert_eq!(stale.diagnostics.len(), 1);
|
||||
|
||||
// The text changed before the call (the file watcher reloaded it), so the caller saw no
|
||||
// change of its own: the old publication is still not fresh.
|
||||
h.mgr.did_change(doc, 2, &[change(rng(0, 0, 0, 3), "good")], &|| "good\n".to_owned());
|
||||
let reloaded = h.mgr.check_document(doc, None, Duration::from_millis(800), &cancel).await.unwrap();
|
||||
assert!(reloaded.servers[0].incomplete, "{:?}", reloaded.servers);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
|
||||
@@ -61,8 +61,9 @@ pub fn artifacts_url(group: &str, prefix: &str) -> Option<String> {
|
||||
|
||||
/// The published version metadata of one artifact on Central.
|
||||
pub fn metadata_url(group: &str, artifact: &str) -> Option<String> {
|
||||
(part(group, 200) && part(artifact, 100)).then(|| {
|
||||
let path = group.split('.').chain(artifact.split('.')).collect::<Vec<_>>().join("/");
|
||||
// The group's dots are directories; an artifact id is one directory, dots and all.
|
||||
(part(group, 200) && part(artifact, 100) && artifact != "." && artifact != "..").then(|| {
|
||||
let path = group.split('.').chain([artifact]).collect::<Vec<_>>().join("/");
|
||||
format!("https://repo1.maven.org/maven2/{path}/maven-metadata.xml")
|
||||
})
|
||||
}
|
||||
@@ -146,6 +147,11 @@ mod tests {
|
||||
metadata_url("org.apache.maven", "maven-core").as_deref(),
|
||||
Some("https://repo1.maven.org/maven2/org/apache/maven/maven-core/maven-metadata.xml")
|
||||
);
|
||||
assert_eq!(
|
||||
metadata_url("org.eclipse.platform", "org.eclipse.core.runtime").as_deref(),
|
||||
Some("https://repo1.maven.org/maven2/org/eclipse/platform/org.eclipse.core.runtime/maven-metadata.xml")
|
||||
);
|
||||
assert_eq!(metadata_url("org.example", ".."), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -218,7 +218,7 @@ describe("plugin-backed content editor tabs", () => {
|
||||
});
|
||||
await waitFor(() => expect(diffs).toHaveLength(1));
|
||||
expect(diffs[0]).toEqual({
|
||||
path: "service.toml",
|
||||
path: "plugin-diff:/service.toml",
|
||||
title: "Diff: service.toml",
|
||||
left: { type: "text", text: 'name = "demo"\nreplicas = 2\n', label: "Live" },
|
||||
right: { type: "text", text: 'name = "demo"\nreplicas = 5\n', label: "Edited" },
|
||||
@@ -239,18 +239,26 @@ describe("plugin-backed content editor tabs", () => {
|
||||
openPluginDiffRequest({ left: "", right: "b\n", title: "MR !12" }, 10);
|
||||
expect(diffs).toEqual([
|
||||
{
|
||||
path: "src/review.ts",
|
||||
path: "plugin-diff:/review.ts",
|
||||
title: "src/review.ts",
|
||||
left: { type: "text", text: "a\n", label: "Target" },
|
||||
right: { type: "text", text: "b\n", label: "After" },
|
||||
},
|
||||
{
|
||||
path: "MR !12",
|
||||
path: "plugin-diff:/MR !12",
|
||||
title: "MR !12",
|
||||
left: { type: "text", text: "", label: "Before" },
|
||||
right: { type: "text", text: "b\n", label: "After" },
|
||||
},
|
||||
]);
|
||||
// A file name only names the syntax: never a path on disk (a UNC share, say).
|
||||
openPluginDiffRequest({ left: "a", right: "b", fileName: "\\\\attacker\\share\\x.rs" }, 10);
|
||||
openPluginDiffRequest({ left: "a", right: "b", title: "C:\\Users\\x\\y.ts" }, 10);
|
||||
expect(diffs.slice(2).map((d) => (d as { path: string }).path)).toEqual([
|
||||
"plugin-diff:/x.rs",
|
||||
"plugin-diff:/y.ts",
|
||||
]);
|
||||
diffs.splice(2);
|
||||
expect(() => openPluginDiffRequest({ left: "a", right: 1 }, 10)).toThrow();
|
||||
expect(() => openPluginDiffRequest({ left: "a", right: "b", fileName: "x\ny" }, 10)).toThrow();
|
||||
expect(() => openPluginDiffRequest({ left: "a", right: "b" }, 4 << 20)).toThrow();
|
||||
|
||||
@@ -176,9 +176,22 @@ function validDiff(v: unknown): ContentTabDiff | undefined {
|
||||
}
|
||||
|
||||
/** Opens a plugin's two texts in the diff viewer; `path` picks the syntax highlighting. */
|
||||
export function openPluginDiff(diff: ContentTabDiff, path: string, title: string) {
|
||||
/**
|
||||
* A plugin diff's path: the file name only picks the syntax highlighting. It is virtual, so the
|
||||
* diff viewer never probes or opens a file of that name (a plugin may lack file system access).
|
||||
*/
|
||||
export function pluginDiffPath(name: string): string {
|
||||
const base =
|
||||
name
|
||||
.split(/[\\/]/)
|
||||
.pop()
|
||||
?.replace(/[\u0000-\u001f:]/g, "") || "diff";
|
||||
return `plugin-diff:/${base}`;
|
||||
}
|
||||
|
||||
export function openPluginDiff(diff: ContentTabDiff, name: string, title: string) {
|
||||
void executeAction("vcs.openDiff", {
|
||||
path,
|
||||
path: pluginDiffPath(name),
|
||||
title: diff.title ?? title,
|
||||
left: { type: "text", text: diff.left, label: diff.leftLabel ?? tx("Before") },
|
||||
right: { type: "text", text: diff.right, label: diff.rightLabel ?? tx("After") },
|
||||
|
||||
@@ -37,6 +37,7 @@ import { call, commands } from "@/platform/ipc";
|
||||
import { bindingsFor, normalizeBinding, shortcutLabel, strokeOf } from "@/platform/keymap";
|
||||
import { tx } from "@/platform/i18n";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { isVirtualUri } from "@/platform/virtualDocs";
|
||||
import { IconButton, Select, Spinner, T, showContextMenu } from "@/ui";
|
||||
import { registerSoftWrapSurface } from "@/workbench/softWrap";
|
||||
import { editorService, type EditorInput } from "@/workbench/contributions";
|
||||
@@ -819,7 +820,8 @@ export function DiffEditor({
|
||||
const fileSource = sideFile(left, path) ?? sideFile(right, path);
|
||||
const [onDisk, setOnDisk] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (fileSource) return;
|
||||
// A virtual path (a plugin's text diff) names no file on disk.
|
||||
if (fileSource || isVirtualUri(path)) return;
|
||||
let alive = true;
|
||||
void call(commands.fsProbe(path)).then(
|
||||
() => alive && setOnDisk(path),
|
||||
|
||||
Reference in new issue
Block a user