Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ dev.lithe.plugin.java-support/
- 不要为了恢复某个语言的调试或测试,把能力目录的合并规则改成“插件没声明就保留宿主原有能力”,也不要在目录或注册表里按 `java` 放行。前者让 PHP、Go 的插件停用后宿主重新运行内置工具,后者是按名字的特判。
- 不要往插件包里写任何东西。可写状态用 `${stateDir}`、`${workspaceStateDir}`,或像 JDT 那样由集成放进宿主缓存。

### Java 调用补全留空实参

构造器和方法补全由共享 JDT 适配器设置 `java.completion.guessMethodArguments: "off"`,Windows 和 macOS 一致。输入 `new No` 后接受 Node 候选得到 `Node()`,括号内保留光标,由用户填写实参;方法候选仍可展示签名,自动导入和其他语言的 snippet 不受影响。初始化、配置通知及按 section 拉取配置必须返回相同值,防止 JDT LS 后续恢复参数占位。

来源是当前固定的 [JDT LS v1.61.0](https://github.com/eclipse-jdtls/eclipse.jdt.ls/tree/v1.61.0):`Preferences.java` 默认选择 `INSERT_PARAMETER_NAMES`,`CompletionProposalReplacementProvider.appendGuessingCompletion` 在 `OFF` 时只插入光标。正确做法是使用该上游设置,同时保留插件声明的其他 completion 项;不要在 Monaco 中用正则删除参数或禁用通用 snippet。后两种方案会误伤方法体模板、其他语言和补全解析后的自动导入。代价是 Java 默认不再提供可逐个替换的参数名占位,JDT LS 也会将构造器重载候选合并为类型候选;这次不增加新的设置界面。

`java_completion_leaves_arguments_empty_across_configuration_channels` 和 `shared/fixtures/lsp/java-completion-v1.json` 验证三个配置入口及插件其他 completion 项的保留;实机验收仍需分别确认两端 Tab 接受候选后的位置和自动导入。
运行会话保留初始化时插件声明的 completion 对象,配置通知和 `java`、`java.completion` 及其叶节点请求均从同一份对象应用 `guessMethodArguments=off`。例如插件声明的 `favoriteStaticMembers`、`filteredTypes` 不得在初始化后被通知或拉取结果清空;引擎测试同时检查真实会话接线,不能只用默认设置证明三个入口一致。

## 考虑过的备选方案

### 只把文件搬进插件包,宿主继续扫描目录
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,16 @@ runner 上可能出现断言都通过、计时工具仍因超过十秒而失败
持久化观察和退出信号仍保持原有短期限,单测试仍受 15 秒计时上限约束。
正确做法是等待明确的就绪事件,不通过固定睡眠同步,也不修改产品进程行为。

Windows 的真实 Git 集成测试按实测成本设置单例预算,普通用例仍为 15 秒。
双仓库分别确认推送目标的用例会初始化两个工作仓库与两个裸远端,先检查
三种确认状态,再提交并推送两个仓库;同一提交在真实 JDT 任务中用时 12.3 秒,
在 SharedRust 任务中触发了 15 秒进程期限。本机跟踪 287 次 Git 进程全部退出,
没有单次 Git 内部操作超过 0.6 秒,但完整测试耗时 49.9 秒,30 秒预算也不足。
两个任务都仅给这个用例 60 秒,
保留每次 Git 请求的期限、进程树清理和整套测试期限。正确做法是给已测量的
外部进程成本配置精确用例预算;不要统一放宽所有测试或用清缓存重编译掩盖
测试超时。具体预算与证据维护在 `write-stable-tests` 的 Windows/Rust 指引中。

CI 缓存 Cargo fingerprints、build script outputs 和依赖 outputs,不缓存
最终可执行文件。缓存覆盖 `rust/target/macos` 的 Rust Core 和 `rust/target`
的数据库辅助 crate;缓存键必须包含运行器架构、编译器、Xcode/SDK/macOS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,15 @@ integration cases create multiple repositories/remotes and run real Git pushes.
Windows run 39947608155 reached the default 15-second deadline in
`git_workspace_commit_push_confirms_each_repository_separately` at 15.16 seconds
without an assertion failure; this replaces the previous single-case allowance
for submodule publication. Other
for submodule publication. That case additionally gets 60 seconds in both
SharedRust and real-JDT. It initializes two working repositories and two bare
remotes, prepares three independent confirmation states, then commits and pushes
both repositories. A local Windows profile completed all assertions in 49.9
seconds across 287 Git processes, with every Git process exiting and no
individual Git operation taking more than 0.6 seconds internally, so the
module's 30-second allowance is not enough for it. Each Git request retains its
native deadline and process-tree cleanup, and the suite deadline remains
unchanged. Other
multi-repository coverage includes
`tests::git::git_write_updates_a_noncurrent_branch_without_switching_head`,
which also gets 30 seconds in both SharedRust and real-JDT lanes: it builds a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ function Invoke-TimedRustTests {
# These retained-path Shelf cases also perform four preview/apply cycles.
$arguments += @("--test-budget", "tests::git_patch_exchange::shelf_roundtrip_preserves_staged_deletions_with_retained_paths=60000")
$arguments += @("--test-budget", "tests::git_patch_exchange::shelf_roundtrip_preserves_staged_rename_with_recreated_source=60000")
# Two repositories, three confirmation previews and two pushes invoke
# 287 Git processes; this integration case measured 49.9s on Windows.
$arguments += @("--test-budget", "tests::git_workspace_commit::git_workspace_commit_push_confirms_each_repository_separately=60000")
}
return $arguments
}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ jobs:
--test-budget tests::git_history_rewrite::native_rebase_skip_omits_conflicted_message_from_later_squash=45000 `
--test-budget tests::git_remote_checkout::=30000 `
--test-budget tests::git_workspace_commit::git_workspace_commit_push_checks_submodule_publication_before_updating_the_remote=30000 `
--test-budget tests::git_workspace_commit::git_workspace_commit_push_confirms_each_repository_separately=30000 `
--test-budget tests::git_workspace_commit::git_workspace_commit_push_confirms_each_repository_separately=60000 `
--test-budget tests::git::git_write_squashes_=30000 `
--test-budget tests::git::git_write_deletes_a_local_commit_=30000 `
--test-budget tests::git::git_write_edits_a_local_commit_message_=30000 `
Expand Down
61 changes: 60 additions & 1 deletion rust/lithe-core/src/lsp/interface/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,9 @@ struct RuntimeSession {
plugin_configuration: Mutex<Option<Arc<Value>>>,
/// JDKs JDT LS may bind projects to, one per execution environment.
jdt_java_runtimes: Vec<JdtJavaRuntime>,
/// Plugin completion options fixed at initialize, retained for later Java
/// configuration notifications and section pulls through the same adapter.
jdt_completion: Option<Value>,
/// Gradle import settings JDT LS holds. Fixed for the session's lifetime
/// because the wrapper is a property of the workspace root at startup.
jdt_gradle_configuration: Option<JdtGradleConfiguration>,
Expand Down Expand Up @@ -960,6 +963,7 @@ impl RuntimeSession {
/// Java settings Lithe owns for this session, as sent to JDT LS.
fn jdt_settings<'a>(&'a self, maven: Option<&'a JdtMavenConfiguration>) -> JdtSettings<'a> {
JdtSettings {
completion: self.jdt_completion.as_ref(),
maven,
gradle: self.jdt_gradle_configuration.as_ref(),
java_runtimes: &self.jdt_java_runtimes,
Expand Down Expand Up @@ -1552,6 +1556,12 @@ impl LspEngine {
let java_build_timeout = Duration::from_millis(request.java_build_timeout_milliseconds);
let shutdown_timeout = Duration::from_millis(request.shutdown_timeout_milliseconds);
let jdt_java_runtimes = jdt_java_runtimes(&request.java_runtimes);
let jdt_completion = request
.initialization_options
.as_ref()
.and_then(|options| options.pointer("/settings/java/completion"))
.filter(|completion| completion.is_object())
.cloned();
let workspace_configuration =
checked_workspace_configuration(request.workspace_configuration)?;
let initialize = client_initialize_for_plugin(
Expand All @@ -1564,6 +1574,7 @@ impl LspEngine {
request.initialization_options,
&java_extension_bundle_paths,
JdtSettings {
completion: jdt_completion.as_ref(),
maven: jdt_maven_configuration.as_deref(),
gradle: request.java_gradle.as_ref(),
java_runtimes: &jdt_java_runtimes,
Expand Down Expand Up @@ -1595,6 +1606,7 @@ impl LspEngine {
maven_update_order: Mutex::new(()),
plugin_configuration: Mutex::new(workspace_configuration.map(Arc::new)),
jdt_java_runtimes,
jdt_completion,
jdt_gradle_configuration: request.java_gradle,
root_uri: request.root_uri,
outbound_order: Mutex::new(()),
Expand Down Expand Up @@ -8671,6 +8683,10 @@ mod tests {

#[test]
fn java_start_enables_and_normalizes_class_file_navigation() {
let completion_fixture: Value = serde_json::from_str(include_str!(
"../../../../../shared/fixtures/lsp/java-completion-v1.json"
))
.unwrap();
let root = std::env::temp_dir().join(format!(
"lithe-core-java-navigation-tests-{}",
std::process::id()
Expand All @@ -8693,7 +8709,8 @@ mod tests {
});
request.initialization_options = Some(json!({
"extendedClientCapabilities": { "customCapability": true },
"bundles": ["/plugins/catalog.jar"]
"bundles": ["/plugins/catalog.jar"],
"settings": { "java": { "completion": completion_fixture["pluginCompletion"] } }
}));
});
let initialize = harness
Expand Down Expand Up @@ -8725,6 +8742,48 @@ mod tests {
.server
.complete_java_initialize(json!({ "definitionProvider": true }));
harness.await_state(LspLifecycleState::Ready);
let expected_completion = &completion_fixture["javaSettings"]["completion"];
assert_eq!(
initialize["params"]["initializationOptions"]["settings"]["java"]["completion"],
*expected_completion,
);
assert!(harness
.server
.await_notification("workspace/didChangeConfiguration"));
let notification = harness
.server
.messages()
.into_iter()
.find(|message| message["method"] == "workspace/didChangeConfiguration")
.unwrap();
assert_eq!(
notification["params"]["settings"]["java"]["completion"],
*expected_completion
);
harness.server.send(json!({
"jsonrpc": "2.0", "id": "java-completion-config", "method": "workspace/configuration",
"params": { "items": completion_fixture["workspaceConfigurationSections"]
.as_array().unwrap().iter().map(|section| json!({ "section": section })).collect::<Vec<_>>() }
}));
let configuration = await_reply(&harness, "java-completion-config");
assert_eq!(
configuration["result"][0]["completion"],
*expected_completion
);
assert_eq!(configuration["result"][1], *expected_completion);
assert_eq!(
configuration["result"][2],
expected_completion["guessMethodArguments"]
);
assert_eq!(
configuration["result"][3],
expected_completion["favoriteStaticMembers"]
);
assert_eq!(
configuration["result"][4],
expected_completion["filteredTypes"]
);
assert_eq!(configuration["result"][5], Value::Null);
let uri = "file:///workspace/Main.java";
harness.sync(uri, "class Main { String value; }");
let operation_id = harness.request(LspSemanticOperation::Definition, uri);
Expand Down
101 changes: 100 additions & 1 deletion rust/lithe-core/src/lsp/languages/jdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ pub(crate) struct JdtJavaRuntime {
/// Values Lithe owns in JDT LS's `settings.java`, borrowed from one session.
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct JdtSettings<'a> {
/// Plugin-declared completion options from this session's initialization;
/// all leaves survive except the provider-owned guessMethodArguments.
pub completion: Option<&'a Value>,
pub maven: Option<&'a JdtMavenConfiguration>,
/// Gradle import settings, present only for Gradle workspaces.
pub gradle: Option<&'a JdtGradleConfiguration>,
Expand Down Expand Up @@ -443,7 +446,7 @@ pub(crate) fn adapt_initialization_options(
/// initialization options.
///
/// Catalog keys that the provider does not own are preserved, while the Maven,
/// build, and code-lens values Lithe depends on stay authoritative.
/// build, completion, and code-lens values Lithe depends on stay authoritative.
fn merge_java_settings(options: &mut Map<String, Value>, provider_settings: Value) {
let settings = options.entry("settings").or_insert_with(|| json!({}));
if !settings.is_object() {
Expand All @@ -467,6 +470,19 @@ fn merge_java_settings(options: &mut Map<String, Value>, provider_settings: Valu
return;
};
for (key, value) in provider_java {
if key == "completion" {
let completion = java.entry(key).or_insert_with(|| json!({}));
if !completion.is_object() {
*completion = json!({});
}
completion.as_object_mut().unwrap().extend(
value
.as_object()
.expect("completion settings are an object")
.clone(),
);
continue;
}
// `java.import.gradle` is the only `import` leaf Lithe owns. The options
// Lithe deliberately never guesses (`java.home`, `version`,
// `offline.enabled`, ...) have to survive, so the owned leaves merge
Expand Down Expand Up @@ -1074,6 +1090,9 @@ fn java_settings(settings: JdtSettings<'_>) -> Value {
"configuration": {
"updateBuildConfiguration": "automatic"
},
// JDT LS otherwise inserts parameter names into calls. Its "off"
// mode keeps the argument list empty and the snippet cursor inside.
"completion": java_completion_settings(settings.completion),
"inlayHints": {
"parameterNames": {
"enabled": "all"
Expand Down Expand Up @@ -1104,6 +1123,17 @@ fn java_settings(settings: JdtSettings<'_>) -> Value {
java
}

/// Retains the plugin's completion leaves while applying Lithe's argument policy.
/// Invalid section shapes are normalized without mutating the session input.
fn java_completion_settings(declared: Option<&Value>) -> Value {
let mut completion = declared
.and_then(Value::as_object)
.cloned()
.unwrap_or_default();
completion.insert("guessMethodArguments".to_string(), json!("off"));
Value::Object(completion)
}

fn java_runtimes_value(runtimes: &[JdtJavaRuntime]) -> Value {
Value::Array(
runtimes
Expand Down Expand Up @@ -1163,6 +1193,15 @@ fn java_configuration_for_section(section: Option<&str>, settings: JdtSettings<'
let gradle_configuration = settings.gradle;
match section {
Some("java") => java_settings(settings)["java"].clone(),
Some("java.completion") => java_settings(settings)["java"]["completion"].clone(),
Some(section) if section.starts_with("java.completion.") => {
let completion = java_completion_settings(settings.completion);
section["java.completion.".len()..]
.split('.')
.try_fold(&completion, |value, key| value.get(key))
.cloned()
.unwrap_or(Value::Null)
}
Some("java.inlayHints") => json!({
"parameterNames": {
"enabled": "all"
Expand Down Expand Up @@ -1424,6 +1463,7 @@ mod tests {
maven: None,
gradle: None,
java_runtimes: &runtimes,
..Default::default()
};
let expected = json!([{ "name": "JavaSE-25", "path": "C:/Program Files/Java/jdk-25" }]);
let options = adapt_initialization_options("java", None, &[], settings).unwrap();
Expand Down Expand Up @@ -1462,6 +1502,7 @@ mod tests {
maven: Some(configuration),
gradle: None,
java_runtimes: &[],
..Default::default()
}
}

Expand All @@ -1470,6 +1511,7 @@ mod tests {
maven: None,
gradle: Some(configuration),
java_runtimes: &[],
..Default::default()
}
}

Expand Down Expand Up @@ -1968,6 +2010,63 @@ mod tests {
assert_eq!(options["workspace"]["custom"], true);
}

#[test]
fn java_completion_leaves_arguments_empty_across_configuration_channels() {
// Initialization, notifications and pull configuration must agree, or
// JDT LS can restore parameter placeholders after the first completion.
let fixture: Value = serde_json::from_str(include_str!(
"../../../../../shared/fixtures/lsp/java-completion-v1.json"
))
.expect("Java completion fixture should be valid");
let expected = &fixture["javaSettings"]["completion"];
let settings = JdtSettings {
completion: Some(&fixture["pluginCompletion"]),
..Default::default()
};
let options = adapt_initialization_options(
"java",
Some(json!({ "settings": { "java": { "completion": fixture["pluginCompletion"] } } })),
&[],
settings,
)
.unwrap();
assert_eq!(
options["settings"]["java"]["completion"]["guessMethodArguments"],
expected["guessMethodArguments"]
);
assert_eq!(
options["settings"]["java"]["completion"]["favoriteStaticMembers"],
json!(["java.util.Objects.*"])
);

assert_eq!(options["settings"]["java"]["completion"], *expected);
let notification = initialized_notification("java", settings).unwrap();
assert_eq!(
notification.params["settings"]["java"]["completion"],
*expected
);
let items = fixture["workspaceConfigurationSections"]
.as_array()
.unwrap()
.iter()
.map(|section| WorkspaceConfigurationItem {
scope_uri: None,
section: Some(section.as_str().unwrap().to_string()),
})
.collect::<Vec<_>>();
let values = workspace_configuration("java", &items, settings).unwrap();
assert_eq!(values[0]["completion"], *expected);
assert_eq!(values[1], *expected);
assert_eq!(values[2], expected["guessMethodArguments"]);
assert_eq!(values[3], expected["favoriteStaticMembers"]);
assert_eq!(values[4], expected["filteredTypes"]);
assert_eq!(values[5], Value::Null);
assert_eq!(
fixture["pluginCompletion"]["guessMethodArguments"],
"insertParameterNames"
);
}

#[test]
fn java_initialization_options_carry_settings_without_a_maven_context() {
let options =
Expand Down
Loading
Loading