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
113 changes: 87 additions & 26 deletions MIGRATION.md

Large diffs are not rendered by default.

16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@

> **进行中**:本仓库正在从 Lithe 主仓库拆分出来,目标是 Lithe 宿主(Rust Core 和 macOS 应用)不再包含任何 Java 专属逻辑,
> Java 的领域规则全部由本插件提供,宿主只提供通用插件接口。插件现在带一个 Rust 后端:`plugin.json` 声明了 `backend` 2,
> 后端目前承担 Maven/Gradle 导入设置的中转(见“工作方式”)。语言服务器仍然由宿主的 JDT 集成启动
> (`launch.kind` 为 `runtime`,带 `hostIntegration`);改为后端启动、去掉 `hostIntegration`、由插件接管运行/调试/测试,
> 要等宿主提供 API-23(`toolchains.list`),清单见 `MIGRATION.md` 第 6 节和第 10 节。在那之前 Lithe 仍然内置 Java Support
> (旧 ID `dev.lithe.plugin.java-support`),本仓库发布的版本标为预发布,原因见 `MIGRATION.md` 第 9 节。
> 后端承担 Maven/Gradle 导入设置的中转(见“工作方式”),并解析语言服务器的启动:`plugin.json` 的 `languageServers[0].launch`
> 是 `kind: "backend"`、函数 `java.resolveLaunch`(`serverProcess` 3),不再有 `hostIntegration`;后端在 `backend/src/launch.rs`
> 里定位包内的 Equinox 启动器、按架构的配置目录、Lombok 代理、java-debug 与 java-test 扩展包和随包 JDK,拼出 JDT LS 的命令行、
> 初始化选项和 `language/status` 就绪门。由插件接管运行/调试/测试还在进行,清单见 `MIGRATION.md` 第 6 节和第 10 节。
> 在宿主去掉内置 Java Support(旧 ID `dev.lithe.plugin.java-support`)之前,本仓库发布的版本标为预发布,原因见 `MIGRATION.md` 第 9 节。
> Maven Archetype 生成器、Maven 与 Gradle 的项目模板已经移交 lithe-plugin-maven 和 lithe-plugin-gradle;
> 在这两个插件接手模板之前不要发布本插件的新版本,否则“新建项目”会少掉 Maven 和 Gradle 选项。

Expand All @@ -26,8 +27,8 @@
| 功能 | 说明 |
| --- | --- |
| 语言服务 | 用打包的 JDT LS(含 Lombok)提供补全、诊断、导航;JDK 随包提供,只用来运行语言服务器,项目自己的 SDK 仍由用户选择 |
| 运行与调试入口 | 识别 `main` 方法和测试方法,生成运行/调试标记 |
| 测试 | 发现 JUnit 测试项,生成测试选择器,并驱动 java-debug 与 vscode-java-test 运行器 |
| 运行与调试 | 在 `main` 方法和测试旁显示 Run/Debug 行内动作;点击后后端经 JDT LS 构建并解析类路径,用用户选择的 JDK 通过宿主 Run 窗口启动,调试经 java-debug 连接;控制台里的堆栈行可点击跳转 |
| 测试 | 发现 JUnit/TestNG 测试项,按类或方法启动 Eclipse JUnit 运行器(TestNG 用 vscode-java-test 运行器),运行结束后显示通过/失败计数 |
| Spring Boot / MyBatis | 索引 Spring 端点与 MyBatis 映射,支持跳转 |
| 新建项目 | 两个声明式项目生成器:Java(无构建系统,可选示例代码)、Spring Boot;Maven 原型和 Maven/Gradle 项目模板由 Maven、Gradle 插件提供 |
| Maven / Gradle 工程 | 导入设置(settings 文件、本地仓库、offline、Gradle user home 等)由 Maven、Gradle 插件通过 `languageImport.settings` 提供,Java 不计算也不解析它们;后端收下后推给 JDT LS,Maven profiles 变化时触发 `java.project.updateSettings` |
Expand All @@ -54,6 +55,9 @@
- 当前不推送 `java.configuration.runtimes`:插件后端还没有列出 JDK 安装的宿主方法,等 API-23(`toolchains.list`)提供。JDT LS 不会因为缺这个键而删除已登记的 VM。

细节见 `MIGRATION.md` 第 5 节。
- **运行/调试/测试**在 `backend/src/run.rs`:用宿主的编辑器动作(`editor/registerActions`)提供行内 Run/Debug 标记,
用 `run/start` 启动进程(进程用清单 `requiresTools: ["java"]` 引用的 JDK 工具定义),测试结果由后端自己在回环端口上监听
JUnit 运行器并用窗口消息显示,输出链接匹配器把堆栈行解析成工作区文件。细节见 `MIGRATION.md` 第 5.9 节。
- **运行时只读**:插件包安装后在运行时只读。JDT LS、Lombok、java-debug、测试运行器、JDK 和后端二进制都在构建阶段固定来源、校验哈希(或由锁定的源码编译)后放进包里,
运行时状态只写宿主提供的状态目录,不写包目录。

Expand Down
1,122 changes: 1,122 additions & 0 deletions backend/src/launch.rs

Large diffs are not rendered by default.

121 changes: 109 additions & 12 deletions backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,18 @@
//! other plugins contribute. The plugin manifest declares it as the plugin's
//! `backend`.
//!
//! The Java rules themselves still run in the host for the editor providers,
//! run configurations and the Java test workflow; the backend is where they
//! move, one host API at a time (see `MIGRATION.md`).
//! The run, debug and test actions of Java files are the backend's own as
//! well ([`run`]): it collects them through editor actions and starts the
//! processes through the host's `run/start` method. What still waits for a
//! host API is listed in `MIGRATION.md`.
//!
//! # Language server launch (serverProcess 3)
//!
//! The manifest declares the `java` language server with
//! `launch.kind: "backend"`, so the host asks [`launch`] for the JDT LS
//! command line, initialization options and startup settings before each
//! start. The launch reads the settings the [`settings_session`] knows for
//! the workspace, so a restart starts with the last pushed settings.
//!
//! # Cross-plugin settings (API-35)
//!
Expand Down Expand Up @@ -38,9 +47,16 @@
//! `settings_session::SESSION_CATEGORIES`. The backend never writes to
//! stderr, and a fact carries no path, source or tool output.

pub mod launch;
pub mod plugin_functions;
pub mod run;
pub mod settings_session;

use crate::launch::LAUNCH_FUNCTION_ID;
use crate::run::{
RunSession, COLLECT_FUNCTION_ID, LANGUAGE_SERVER_NOTIFICATION, RERUN_FUNCTION_ID,
RESOLVE_LINK_FUNCTION_ID, RUN_ACTION_FUNCTION_ID, RUN_DID_EXIT_NOTIFICATION,
};
use crate::settings_session::{
HostServices, SettingsSession, LANGUAGE_SERVER_STATE_NOTIFICATION, SETTINGS_CHANGED_FUNCTION_ID,
};
Expand All @@ -66,10 +82,19 @@ const NOTIFICATION_OPERATION: &str = "notification";
/// `languageImport.settingsChanged` is a key operation: another plugin
/// reports a change a user made, it happens rarely, and its success is the
/// only trace that the change reached the language server.
/// `java.resolveLaunch` is one too: it runs once per language server start
/// and its success is the only trace of which package files the server
/// started from. `java.runAction` and `java.rerun` are key operations
/// because each is one user click that starts a process; collection and
/// output link resolution run on every edit and every output line, so their
/// successes are suppressed.
pub const KEY_OPERATIONS: &[&str] = &[
"debug.javaTestLaunch",
"execution.planLaunchCommand",
"java.prepareLaunch",
LAUNCH_FUNCTION_ID,
RERUN_FUNCTION_ID,
RUN_ACTION_FUNCTION_ID,
SETTINGS_CHANGED_FUNCTION_ID,
"runConfig.selectJava",
];
Expand All @@ -78,8 +103,9 @@ pub const KEY_OPERATIONS: &[&str] = &[
///
/// A function can only be suppressed once it is registered, so the successes
/// of the functions outside [`KEY_OPERATIONS`] are suppressed after the
/// registration loop. `languageImport.settingsChanged` is registered after
/// that loop and is a key operation, so it needs no suppression.
/// registration loop. `languageImport.settingsChanged` and
/// `java.resolveLaunch` are registered after that loop and are key
/// operations, so they need no suppression.
///
/// Every input is a compile-time constant, so the result is the same on each
/// run: it fails only when a function ID is invalid, repeated or over the
Expand Down Expand Up @@ -120,9 +146,69 @@ pub fn backend() -> Result<Backend, SdkError> {
Ok(())
}
})?;
backend.function(SETTINGS_CHANGED_FUNCTION_ID, move |params, context| {
settings_changed(&session, params, context)
backend.function(SETTINGS_CHANGED_FUNCTION_ID, {
let session = Arc::clone(&session);
move |params, context| settings_changed(&session, params, context)
})?;
backend.function(LAUNCH_FUNCTION_ID, move |params, context| {
launch::resolve_launch_function(&session, params, context)
})?;

// The run session registers the editor actions of every workspace context
// and keeps the result listener of each test run; both happen outside a
// function call, so it holds the backend handle too.
let run_session = Arc::new(RunSession::new(backend.handle()));
let report: run::Report = {
let handle = backend.handle();
Arc::new(move |code: &str, count: u64| {
let _ = handle
.diagnostics()
.degraded(NOTIFICATION_OPERATION, code, Some(count));
})
};
backend.on_initialize({
let run_session = Arc::clone(&run_session);
let report = Arc::clone(&report);
move |_initialize| {
run_session.sync_after_initialize(Arc::clone(&report));
Ok(())
}
});
backend.on_contexts_changed({
let run_session = Arc::clone(&run_session);
let report = Arc::clone(&report);
move |_params| {
run_session.sync_contexts_reporting(&*report);
Ok(())
}
});
backend.on_host_notification(RUN_DID_EXIT_NOTIFICATION, {
let run_session = Arc::clone(&run_session);
move |params| {
run_session.run_did_exit(&params);
Ok(())
}
})?;
backend.on_host_notification(LANGUAGE_SERVER_NOTIFICATION, {
let run_session = Arc::clone(&run_session);
let report = Arc::clone(&report);
move |params| {
run_session.did_receive_notification(&params, &*report);
Ok(())
}
})?;
backend.function(COLLECT_FUNCTION_ID, run::collect_actions_function)?;
backend.suppress_success_diagnostics(COLLECT_FUNCTION_ID)?;
backend.function(RUN_ACTION_FUNCTION_ID, {
let run_session = Arc::clone(&run_session);
move |params, context| run::run_action_function(&run_session, params, context)
})?;
backend.function(RERUN_FUNCTION_ID, {
let run_session = Arc::clone(&run_session);
move |params, context| run::rerun_function(&run_session, params, context)
})?;
backend.function(RESOLVE_LINK_FUNCTION_ID, run::resolve_output_link_function)?;
backend.suppress_success_diagnostics(RESOLVE_LINK_FUNCTION_ID)?;
Ok(backend)
}

Expand Down Expand Up @@ -263,9 +349,9 @@ mod tests {
.all(|code| lithe_plugin_sdk::is_plugin_error_code(code)));
}

/// An error code names one cause. The codes `settingsChanged` answers
/// with, the domain codes and the diagnostics categories of the settings
/// modules must never share a name.
/// An error code names one cause. The codes `settingsChanged` and
/// `java.resolveLaunch` answer with, the domain codes and the diagnostics
/// categories of the settings and launch modules must never share a name.
#[test]
fn settings_relay_codes_never_share_a_name_with_another_code() {
let domain: Vec<&str> = [
Expand All @@ -290,6 +376,10 @@ mod tests {
.iter()
.chain(settings_session::SESSION_CATEGORIES)
.chain(plugin_functions::RELAY_CATEGORIES)
.chain(launch::LAUNCH_ERROR_CODES)
.chain(launch::LAUNCH_CATEGORIES)
.chain(run::RUN_ERROR_CODES)
.chain(run::RUN_CATEGORIES)
{
assert!(seen.insert(*name), "{name} names two causes");
}
Expand All @@ -309,12 +399,19 @@ mod tests {

#[test]
fn key_operations_are_registered_functions_without_duplicates() {
// The commands, and the one function registered next to them.
// The commands, and the functions registered next to them.
let commands: std::collections::BTreeSet<&str> = SUPPORTED_COMMANDS
.iter()
.chain(SESSION_COMMANDS)
.copied()
.chain([SETTINGS_CHANGED_FUNCTION_ID])
.chain([
SETTINGS_CHANGED_FUNCTION_ID,
LAUNCH_FUNCTION_ID,
COLLECT_FUNCTION_ID,
RUN_ACTION_FUNCTION_ID,
RERUN_FUNCTION_ID,
RESOLVE_LINK_FUNCTION_ID,
])
.collect();
let distinct: std::collections::BTreeSet<&str> = KEY_OPERATIONS.iter().copied().collect();
assert_eq!(
Expand Down
Loading
Loading