From 10c994d3bdc529dc751328ebdbf014d47715a387 Mon Sep 17 00:00:00 2001 From: Yao Jingxi <23722032@bjtu.edu.cn> Date: Fri, 14 Aug 2026 22:36:52 +0800 Subject: [PATCH] add: add comments to the rust core --- .agents/skills/develop-lithe/SKILL.md | 24 ++++ AGENTS.md | 3 +- README.md | 2 +- README.zh-CN.md | 2 +- docs/architecture/repository-layout.md | 14 +++ .../lithe-core/src/execution/configuration.rs | 34 +++++- .../src/execution/detectors/cargo.rs | 2 + .../src/execution/detectors/compose.rs | 3 + rust/lithe-core/src/execution/detectors/go.rs | 2 + .../src/execution/detectors/gradle.rs | 2 + .../src/execution/detectors/make.rs | 2 + .../src/execution/detectors/maven.rs | 2 + .../lithe-core/src/execution/detectors/mod.rs | 6 + .../lithe-core/src/execution/detectors/npm.rs | 3 + .../src/execution/detectors/procfile.rs | 2 + .../src/execution/detectors/python.rs | 3 + .../src/execution/detectors/scan.rs | 5 + .../src/execution/detectors/shell.rs | 2 + rust/lithe-core/src/execution/types.rs | 9 ++ rust/lithe-core/src/git/mod.rs | 38 ++++++ rust/lithe-core/src/languages/java.rs | 15 +++ rust/lithe-core/src/lib.rs | 2 + rust/lithe-core/src/lsp/interface/client.rs | 8 ++ rust/lithe-core/src/lsp/interface/engine.rs | 55 +++++++++ rust/lithe-core/src/lsp/interface/process.rs | 2 + .../lithe-core/src/lsp/interface/transport.rs | 8 ++ rust/lithe-core/src/lsp/interface/types.rs | 27 +++++ rust/lithe-core/src/lsp/languages/catalog.rs | 21 ++++ rust/lithe-core/src/lsp/languages/swift.rs | 2 + rust/lithe-core/src/lsp/lightweight/edits.rs | 6 + .../src/lsp/lightweight/snippets.rs | 4 + .../lithe-core/src/lsp/lightweight/symbols.rs | 14 +++ rust/lithe-core/src/plugins/mod.rs | 109 ++++++++++++++++-- rust/lithe-core/src/project/files.rs | 44 +++++-- rust/lithe-core/src/project/history.rs | 14 +++ rust/lithe-core/src/project/markdown.rs | 4 + rust/lithe-core/src/project/maven.rs | 10 +- rust/lithe-core/src/project/search_index.rs | 9 ++ rust/lithe-core/src/protocol/cancellation.rs | 8 ++ rust/lithe-core/src/protocol/command.rs | 75 ++++++++++++ rust/lithe-core/src/protocol/contracts.rs | 66 +++++++++++ rust/lithe-core/src/protocol/error.rs | 23 ++++ rust/lithe-core/src/protocol/event.rs | 13 ++- rust/lithe-core/src/runtime/dispatcher.rs | 2 + rust/lithe-core/src/runtime/ffi.rs | 36 ++++++ rust/lithe-core/src/tests/project.rs | 2 +- 46 files changed, 712 insertions(+), 27 deletions(-) diff --git a/.agents/skills/develop-lithe/SKILL.md b/.agents/skills/develop-lithe/SKILL.md index d366f57a..f570d01d 100644 --- a/.agents/skills/develop-lithe/SKILL.md +++ b/.agents/skills/develop-lithe/SKILL.md @@ -104,6 +104,30 @@ the existing stack can reasonably avoid. - Add tests in the owning crate for changes to commands, parsing, validation, ordering, cancellation, or serialization. +#### Rust Core comments + +Apply the following comment standard to first-party code under +`rust/lithe-core/`. It does not require comment coverage in the database helpers, +Windows/Tauri Rust crates, generated code, or third-party sources. + +- Write comments in English and keep them accurate when behavior changes. +- Start each production module with a concise `//!` description of its + responsibility or architectural boundary. +- Use `///` for exported APIs, shared request and response types, core domain + types, and C ABI functions. Document ownership and add `# Safety` for unsafe + entry points; describe errors only when the failure contract is not obvious. +- Document enums, structs, variants, and fields whenever their names alone do + not make their semantics, allowed values, units, ownership, or protocol role + immediately clear. This requirement applies to internal types as well as + exported contracts. +- Use `//` inside implementations to explain non-obvious decisions and + constraints involving compatibility, determinism, ordering, security, + performance, or cross-platform behavior. +- In tests, comment the scenario, regression risk, or boundary being protected + when the test name and assertions do not make that intent clear. +- Do not narrate statements, restate descriptive names, or add comments to + trivial accessors and straightforward control flow solely for coverage. + ### Windows React and Tauri - Use Bun for frontend scripts and Tauri 2 for the Windows host. Keep React diff --git a/AGENTS.md b/AGENTS.md index 2c981610..fa4e8e4a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,4 +2,5 @@ Before any work in this repository, load and follow the `develop-lithe` skill at `.agents/skills/develop-lithe/SKILL.md`. That Skill is the single source of -truth for AI coding and verification rules. +truth for AI coding and verification rules, including the required Rust Core +comment standard. diff --git a/README.md b/README.md index 0b9a733a..b5359e60 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ Before submitting a change, run: ./scripts/verify-rust-core.sh ``` -See [Repository layout and shared boundaries](./docs/architecture/repository-layout.md) for directory ownership, cross-platform boundaries, and sharing rules. Include your verification steps and known limitations when submitting a change. +See [Repository layout and shared boundaries](./docs/architecture/repository-layout.md) for directory ownership, cross-platform boundaries, sharing rules, and the required Rust Core comment standard. Include your verification steps and known limitations when submitting a change. ## Project support diff --git a/README.zh-CN.md b/README.zh-CN.md index a47810b1..e36800a6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -213,7 +213,7 @@ open dist/Lithe.app ./scripts/verify-rust-core.sh ``` -目录归属、跨平台边界和共享规则见[仓库目录与共享边界](./docs/architecture/repository-layout.md)。提交功能改动时,请说明验证方式和已知限制。 +目录归属、跨平台边界、共享规则以及 Rust Core 必须遵守的注释规范见[仓库目录与共享边界](./docs/architecture/repository-layout.md)。提交功能改动时,请说明验证方式和已知限制。 ## 项目支持 diff --git a/docs/architecture/repository-layout.md b/docs/architecture/repository-layout.md index 8230ddfc..1a3ed188 100644 --- a/docs/architecture/repository-layout.md +++ b/docs/architecture/repository-layout.md @@ -122,6 +122,20 @@ The dependency direction is `protocol <- domain packages <- runtime/FFI`. A doma Moving Rust files must not change JSON command strings, Serde field names, error codes, or the exported C symbols. Directory-sensitive fixtures and embedded resources must use `CARGO_MANIFEST_DIR` instead of paths derived from a module's current depth. +### Rust Core comment standard + +First-party production modules under `rust/lithe-core/` start with an English +`//!` description of their responsibility or boundary. Exported APIs, shared +request and response structures, core domain types, and C ABI functions use +`///`; unsafe entry points document pointer ownership and `# Safety` +requirements. Enums, structs, variants, and fields whose names do not make +their semantics, allowed values, units, ownership, or protocol role immediately +clear are documented even when they are internal. Implementation comments explain non-obvious compatibility, +determinism, ordering, security, performance, or cross-platform constraints. +They should explain why the code has its shape instead of narrating individual +statements. Tests document scenarios or regression risks only when their names +and assertions are not already sufficient. + ## Ownership rules | Shared Rust Core | Platform-owned adapters | diff --git a/rust/lithe-core/src/execution/configuration.rs b/rust/lithe-core/src/execution/configuration.rs index fba0c937..7e71ebe8 100644 --- a/rust/lithe-core/src/execution/configuration.rs +++ b/rust/lithe-core/src/execution/configuration.rs @@ -1,3 +1,5 @@ +//! Run-configuration schemas, layered overrides, and deterministic generation. + use super::types::{Confidence, Execution}; use crate::languages::JavaRunConfigurationsRequest; use crate::protocol::{invalid_relative_path, CoreError, ErrorCode}; @@ -17,12 +19,14 @@ const SIDECAR_VERSION: u32 = 1; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to validate the layered configuration documents for a workspace. pub struct InspectRequest { pub root: String, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to regenerate detected configurations from the current project tree. pub struct GenerateRequest { pub root: String, #[serde(default)] @@ -33,6 +37,7 @@ pub struct GenerateRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to merge configuration layers and resolve host toolchains. pub struct ResolveRequest { pub root: String, #[serde(default)] @@ -41,8 +46,11 @@ pub struct ResolveRequest { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +/// Host-discovered executable that may satisfy a configuration requirement. pub struct ToolchainCandidate { + /// Host-stable candidate identifier referenced by resolved configurations. pub id: String, + /// Toolchain role such as `java` or `maven`, not a display label. #[serde(rename = "type")] pub kind: String, #[serde(default)] @@ -53,6 +61,7 @@ pub struct ToolchainCandidate { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to turn one resolved configuration into a process launch plan. pub struct LaunchPlanRequest { pub root: String, pub configuration_id: String, @@ -66,8 +75,10 @@ pub struct LaunchPlanRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Editable options applied to a team or machine-local configuration layer. pub struct UpdateOptionsRequest { pub root: String, + /// Persistence layer: `project` for shared configuration or `local` for this host. pub scope: String, pub configuration_id: String, #[serde(default)] @@ -92,10 +103,13 @@ pub struct UpdateOptionsRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to add an explicitly user-authored run configuration. pub struct CreateUserConfigurationRequest { pub root: String, + /// Persistence layer: `project` for shared configuration or `local` for this host. pub scope: String, pub name: String, + /// UI configuration kind mapped to a namespaced provider during creation. #[serde(rename = "type")] pub kind: String, #[serde(default)] @@ -106,6 +120,7 @@ pub struct CreateUserConfigurationRequest { #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] +/// Versioned run-configuration document stored below `.lithe/run`. pub struct RunConfigurationDocument { pub version: u32, #[serde(default)] @@ -188,6 +203,7 @@ fn migrate_configuration_value(item: &mut Value) { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +/// Fingerprint and inputs used to decide whether generated output is stale. pub struct GeneratorMetadata { pub fingerprint: String, #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] @@ -196,12 +212,15 @@ pub struct GeneratorMetadata { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +/// Debug adapter supported by a runnable configuration. pub struct DebugCapability { + /// Stable adapter identifier, currently `jdwp` for JVM configurations. pub adapter: String, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +/// Ecosystem-neutral command, identity, and launch metadata for one runnable item. pub struct RunConfiguration { pub id: String, pub name: String, @@ -231,6 +250,7 @@ pub struct RunConfiguration { pub extensions: BTreeMap, #[serde(default)] pub disabled: bool, + /// Workspace-relative manifest or source file that produced the configuration. #[serde(default, skip_serializing_if = "Option::is_none")] pub source: Option, } @@ -261,6 +281,7 @@ impl RunConfiguration { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +/// Versioned requirements written separately from run configurations. pub struct ToolchainRequirementsDocument { pub version: u32, #[serde(default)] @@ -269,7 +290,9 @@ pub struct ToolchainRequirementsDocument { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] +/// Constraints the host uses when selecting one toolchain candidate. pub struct ToolchainRequirement { + /// Toolchain role matched against [`ToolchainCandidate::kind`]. #[serde(rename = "type")] pub kind: String, #[serde(default)] @@ -284,6 +307,7 @@ pub struct ToolchainRequirement { pub java: Option, } +/// Validates configuration and sidecar documents without mutating the workspace. pub fn inspect(request: InspectRequest) -> Result { let root = existing_root(&request.root)?; let generated = read_document(&root, "run/generated.json")?; @@ -347,6 +371,7 @@ pub fn inspect(request: InspectRequest) -> Result { })) } +/// Detects runnable project entries and writes a deterministic generated layer. pub fn generate(request: GenerateRequest) -> Result { let root = existing_root(&request.root)?; let mut paths = request @@ -729,6 +754,7 @@ fn is_nested_checkout_path(value: &str) -> bool { }) } +/// Merges generated, team, and local layers and selects compatible toolchains. pub fn resolve(request: ResolveRequest) -> Result { let root = existing_root(&request.root)?; let generated = read_document_value(&root, "run/generated.json")?.ok_or_else(|| { @@ -831,6 +857,7 @@ pub fn resolve(request: ResolveRequest) -> Result { })) } +/// Persists editable configuration options in the requested ownership layer. pub fn update_options(request: UpdateOptionsRequest) -> Result { let root = existing_root(&request.root)?; let relative = scope_document(&request.scope)?; @@ -921,6 +948,7 @@ pub fn update_options(request: UpdateOptionsRequest) -> Result })) } +/// Creates a user configuration while preserving stable IDs in existing layers. pub fn create_user_configuration( request: CreateUserConfigurationRequest, ) -> Result { @@ -1023,6 +1051,7 @@ pub fn create_user_configuration( })) } +/// Resolves one configuration into the exact executable, arguments, and environment. pub fn create_launch_plan(request: LaunchPlanRequest) -> Result { let resolved = resolve(ResolveRequest { root: request.root, @@ -2147,8 +2176,7 @@ fn declared_go_version(root: &Path) -> Option { fn declared_python_version(root: &Path) -> Option { let expression = - regex::Regex::new(r#"(?m)^\s*(?:requires-python|python)\s*=\s*[\"']([^\"']+)[\"']"#) - .ok()?; + regex::Regex::new(r#"(?m)^\s*(?:requires-python|python)\s*=\s*["']([^"']+)["']"#).ok()?; highest_version( project_manifest_paths(root, &["pyproject.toml"]) .into_iter() @@ -2263,7 +2291,7 @@ fn declared_java_version(root: &Path) -> Option<(String, Option)> { } } if let Ok(text) = fs::read_to_string(root.join("mise.toml")) { - let expression = regex::Regex::new(r#"(?m)^\s*java\s*=\s*[\"']([^\"']+)[\"']"#).ok()?; + let expression = regex::Regex::new(r#"(?m)^\s*java\s*=\s*["']([^"']+)["']"#).ok()?; if let Some(value) = expression .captures(&text) .and_then(|capture| capture.get(1)) diff --git a/rust/lithe-core/src/execution/detectors/cargo.rs b/rust/lithe-core/src/execution/detectors/cargo.rs index 9757589e..d0350ac9 100644 --- a/rust/lithe-core/src/execution/detectors/cargo.rs +++ b/rust/lithe-core/src/execution/detectors/cargo.rs @@ -1,3 +1,5 @@ +//! Cargo binary discovery from manifests and conventional source layouts. + use super::{Detected, DirectoryContext}; /// Cargo binaries come from `[[bin]]` entries, or implicitly from `src/main.rs`. diff --git a/rust/lithe-core/src/execution/detectors/compose.rs b/rust/lithe-core/src/execution/detectors/compose.rs index 664e90b3..57f6423b 100644 --- a/rust/lithe-core/src/execution/detectors/compose.rs +++ b/rust/lithe-core/src/execution/detectors/compose.rs @@ -1,3 +1,5 @@ +//! Docker Compose service discovery from the standard manifest names. + use super::{Detected, DirectoryContext}; const FILES: &[&str] = &[ @@ -7,6 +9,7 @@ const FILES: &[&str] = &[ "compose.yaml", ]; +/// Returns one service configuration for each declared Compose service. pub fn detect(ctx: &DirectoryContext) -> Vec { let Some(file) = ctx.any_of(FILES) else { return Vec::new(); diff --git a/rust/lithe-core/src/execution/detectors/go.rs b/rust/lithe-core/src/execution/detectors/go.rs index ef4ea7c2..e4c3645b 100644 --- a/rust/lithe-core/src/execution/detectors/go.rs +++ b/rust/lithe-core/src/execution/detectors/go.rs @@ -1,3 +1,5 @@ +//! Go application discovery from module roots and conventional command layouts. + use super::super::types::Confidence; use super::{Detected, DirectoryContext}; diff --git a/rust/lithe-core/src/execution/detectors/gradle.rs b/rust/lithe-core/src/execution/detectors/gradle.rs index e4ea53c8..be27a552 100644 --- a/rust/lithe-core/src/execution/detectors/gradle.rs +++ b/rust/lithe-core/src/execution/detectors/gradle.rs @@ -1,3 +1,5 @@ +//! Gradle service discovery without starting Gradle or evaluating build scripts. + use super::super::types::Confidence; use super::{Detected, DirectoryContext}; diff --git a/rust/lithe-core/src/execution/detectors/make.rs b/rust/lithe-core/src/execution/detectors/make.rs index 19e73ba4..70f87365 100644 --- a/rust/lithe-core/src/execution/detectors/make.rs +++ b/rust/lithe-core/src/execution/detectors/make.rs @@ -1,3 +1,5 @@ +//! Runnable Make target discovery with conservative service classification. + use super::super::types::Confidence; use super::{Detected, DirectoryContext}; diff --git a/rust/lithe-core/src/execution/detectors/maven.rs b/rust/lithe-core/src/execution/detectors/maven.rs index 8f06acde..4231e6f0 100644 --- a/rust/lithe-core/src/execution/detectors/maven.rs +++ b/rust/lithe-core/src/execution/detectors/maven.rs @@ -1,3 +1,5 @@ +//! Maven service discovery from the declared reactor and applied plugins. + use super::super::types::Confidence; use super::{Detected, DirectoryContext}; use crate::project::{declared_modules, DeclaredModule}; diff --git a/rust/lithe-core/src/execution/detectors/mod.rs b/rust/lithe-core/src/execution/detectors/mod.rs index 51c9bab2..759971a9 100644 --- a/rust/lithe-core/src/execution/detectors/mod.rs +++ b/rust/lithe-core/src/execution/detectors/mod.rs @@ -61,6 +61,7 @@ pub struct Detected { } impl Detected { + /// Creates a short-lived or interactive application detection. pub fn application( provider: &str, name: &str, @@ -80,6 +81,7 @@ impl Detected { ) } + /// Creates a long-running service detection. pub fn service( provider: &str, name: &str, @@ -99,6 +101,7 @@ impl Detected { ) } + /// Creates a command expected to run to completion. pub fn task( provider: &str, name: &str, @@ -135,6 +138,7 @@ impl Detected { } } + /// Replaces the default declared confidence with the detector's evidence level. pub fn with_confidence(mut self, confidence: Confidence) -> Self { self.confidence = confidence; self @@ -157,6 +161,7 @@ impl Detected { self } + /// Declares the debug adapter supported by this detection. pub fn with_debug(mut self, adapter: &str) -> Self { self.debug = Some(adapter.to_string()); self @@ -171,6 +176,7 @@ impl Detected { self } + /// Attaches provider-specific metadata without expanding the shared schema. pub fn with_extension(mut self, namespace: &str, value: serde_json::Value) -> Self { self.extensions.insert(namespace.to_string(), value); self diff --git a/rust/lithe-core/src/execution/detectors/npm.rs b/rust/lithe-core/src/execution/detectors/npm.rs index 67a020c3..03f08df1 100644 --- a/rust/lithe-core/src/execution/detectors/npm.rs +++ b/rust/lithe-core/src/execution/detectors/npm.rs @@ -1,3 +1,5 @@ +//! JavaScript package-script discovery and framework classification. + use super::super::types::Confidence; use super::{Detected, DirectoryContext}; use serde_json::Value; @@ -87,6 +89,7 @@ const MANAGERS: &[(&str, &str)] = &[ ("package-lock.json", "npm"), ]; +/// Classifies runnable package scripts using declared dependencies and commands. pub fn detect(ctx: &DirectoryContext) -> Vec { let Some(text) = ctx.read("package.json") else { return Vec::new(); diff --git a/rust/lithe-core/src/execution/detectors/procfile.rs b/rust/lithe-core/src/execution/detectors/procfile.rs index 9b3dccff..2bcd2c1f 100644 --- a/rust/lithe-core/src/execution/detectors/procfile.rs +++ b/rust/lithe-core/src/execution/detectors/procfile.rs @@ -1,3 +1,5 @@ +//! Procfile process discovery for commands that can be launched without a shell. + use super::super::types::Confidence; use super::{Detected, DirectoryContext}; diff --git a/rust/lithe-core/src/execution/detectors/python.rs b/rust/lithe-core/src/execution/detectors/python.rs index 0f6ab41d..13a8e046 100644 --- a/rust/lithe-core/src/execution/detectors/python.rs +++ b/rust/lithe-core/src/execution/detectors/python.rs @@ -1,6 +1,9 @@ +//! Python entry-point and framework discovery from declarations and conventions. + use super::super::types::Confidence; use super::{Detected, DirectoryContext}; +/// Combines declared Python entry points with framework-based conventions. pub fn detect(ctx: &DirectoryContext) -> Vec { let mut detected = pyproject(ctx); detected.extend(frameworks(ctx)); diff --git a/rust/lithe-core/src/execution/detectors/scan.rs b/rust/lithe-core/src/execution/detectors/scan.rs index 6fd51374..b54db4b3 100644 --- a/rust/lithe-core/src/execution/detectors/scan.rs +++ b/rust/lithe-core/src/execution/detectors/scan.rs @@ -1,3 +1,5 @@ +//! Bounded workspace traversal shared by all run-configuration detectors. + use crate::protocol::{CoreError, ErrorCode}; use std::collections::BTreeSet; use std::fs; @@ -64,6 +66,7 @@ pub struct DirectoryContext { } impl DirectoryContext { + /// Captures the readable non-directory entries immediately below `path`. pub fn at(root: &Path, path: &Path) -> Result, CoreError> { let Ok(entries) = fs::read_dir(path) else { return Ok(None); @@ -83,6 +86,7 @@ impl DirectoryContext { })) } + /// Reports whether the directory contains an entry with the exact name. pub fn has(&self, name: &str) -> bool { self.files.contains(name) } @@ -96,6 +100,7 @@ impl DirectoryContext { .find(|name| self.files.contains(*name)) } + /// Reads a known entry as UTF-8, returning no content for unreadable files. pub fn read(&self, name: &str) -> Option { if !self.has(name) { return None; diff --git a/rust/lithe-core/src/execution/detectors/shell.rs b/rust/lithe-core/src/execution/detectors/shell.rs index 54b3a666..173c8e81 100644 --- a/rust/lithe-core/src/execution/detectors/shell.rs +++ b/rust/lithe-core/src/execution/detectors/shell.rs @@ -1,3 +1,5 @@ +//! Runnable recipe discovery for Just and shell-script projects. + use super::super::types::Confidence; use super::{Detected, DirectoryContext}; diff --git a/rust/lithe-core/src/execution/types.rs b/rust/lithe-core/src/execution/types.rs index 9a943b3f..a71acf83 100644 --- a/rust/lithe-core/src/execution/types.rs +++ b/rust/lithe-core/src/execution/types.rs @@ -1,12 +1,18 @@ +//! Shared classification types used by configuration generation and detectors. + use serde::{Deserialize, Serialize}; /// How a configuration behaves once started. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Execution { + /// A user-facing process that may be interactive but is not a background service. Application, + /// A long-running process expected to keep serving until explicitly stopped. Service, + /// A finite command expected to exit after producing its result. Task, + /// A non-process entry that groups other configurations for coordinated launch. Group, } @@ -20,8 +26,11 @@ impl Default for Execution { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Confidence { + /// Inferred from naming or source-layout conventions rather than a declaration. Heuristic, + /// Supported by a manifest, build plugin, dependency, or other project declaration. Declared, + /// Authored by Lithe itself and therefore stronger than detector evidence. Native, } diff --git a/rust/lithe-core/src/git/mod.rs b/rust/lithe-core/src/git/mod.rs index 182ab5b8..d33176ba 100644 --- a/rust/lithe-core/src/git/mod.rs +++ b/rust/lithe-core/src/git/mod.rs @@ -1,3 +1,5 @@ +//! Deterministic Git inspection and mutation behind the shared command contract. + use crate::protocol::{CoreError, ErrorCode}; use crate::protocol::{ GitBlameLineResponse, GitBlameResponse, GitChange, GitCheckoutPreflightResponse, @@ -17,12 +19,14 @@ use std::time::Duration; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request for a deterministic porcelain status snapshot. pub struct GitStatusRequest { pub root: String, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request for the directories and files a host watcher should observe. pub struct GitWatchContextRequest { pub root: String, } @@ -44,6 +48,7 @@ pub struct GitCommandRequest { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] +/// Stable output returned by argument-based Git execution. pub struct GitCommandResponse { pub output: String, pub exit_code: i32, @@ -57,6 +62,7 @@ pub struct GitCommandResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Recovery context when restoring a stash produces conflicts. pub struct GitStashRestoreResponse { pub stash_reference: String, pub conflicted_paths: Vec, @@ -64,13 +70,16 @@ pub struct GitStashRestoreResponse { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Typed mutation request translated into a controlled Git invocation. pub struct GitWriteRequest { pub root: String, + /// Stable mutation discriminator interpreted by [`write`]. pub operation: String, #[serde(default)] pub paths: Vec, #[serde(default)] pub reference: Option, + /// Reference category used by checkout: `local`, `remote`, or `tag`. #[serde(default)] pub reference_kind: Option, #[serde(default)] @@ -83,6 +92,7 @@ pub struct GitWriteRequest { pub remote: Option, #[serde(default)] pub destination: Option, + /// Operation-specific strategy, such as reset mode or pull reconciliation. #[serde(default)] pub mode: Option, #[serde(default)] @@ -99,6 +109,7 @@ pub struct GitWriteRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request for a structured diff suitable for side-by-side rendering. pub struct GitDiffRequest { pub root: String, pub pathspecs: Vec, @@ -118,14 +129,17 @@ pub struct GitDiffRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to apply a patch to the index or working tree. pub struct GitApplyRequest { pub root: String, pub patch: String, + /// Patch target or validation mode, including `stage`, `unstage`, and `worktree`. pub mode: String, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request for bounded commit history from an optional reference. pub struct GitHistoryRequest { pub root: String, #[serde(default)] @@ -136,6 +150,7 @@ pub struct GitHistoryRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request for metadata and parent information about one commit. pub struct GitCommitRequest { pub root: String, pub commit: String, @@ -143,6 +158,7 @@ pub struct GitCommitRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request for the paths changed by one commit. pub struct GitCommitFilesRequest { pub root: String, pub commit: String, @@ -150,6 +166,7 @@ pub struct GitCommitFilesRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to compare a reference with the current checkout. pub struct GitComparisonRequest { pub root: String, pub reference: String, @@ -157,12 +174,14 @@ pub struct GitComparisonRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request for the repository's ordered stash list. pub struct GitStashesRequest { pub root: String, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to identify local edits that would block switching references. pub struct GitCheckoutPreflightRequest { pub root: String, pub reference: String, @@ -170,12 +189,14 @@ pub struct GitCheckoutPreflightRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to find staged files that still contain conflict markers. pub struct GitConflictMarkerRequest { pub root: String, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to determine whether a merge or rebase can start safely. pub struct GitIntegrationPreflightRequest { pub root: String, pub reference: String, @@ -185,18 +206,21 @@ pub struct GitIntegrationPreflightRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to determine whether the tracked branch can fast-forward. pub struct GitPullPreflightRequest { pub root: String, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to inspect an interrupted merge, rebase, cherry-pick, or revert. pub struct GitOperationStateRequest { pub root: String, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request for line attribution on one workspace-relative file. pub struct GitBlameRequest { pub root: String, pub path: String, @@ -210,6 +234,7 @@ fn default_history_limit() -> usize { 300 } +/// Executes an argument-based Git command after validating the workspace root. pub fn command(request: GitCommandRequest) -> Result { let root = validate_root(&request.root)?; execute_git(&root, &request.arguments, request.input) @@ -220,6 +245,7 @@ fn readonly_command(request: GitCommandRequest) -> Result Result { let root = validate_root(&request.root)?; let mut arguments: Vec; @@ -545,6 +571,7 @@ fn execute_git_with_options( }) } +/// Builds a structured working-tree, staged, untracked, or commit diff. pub fn diff(request: GitDiffRequest) -> Result { if request.pathspecs.is_empty() || request.pathspecs.iter().any(|path| !is_safe_pathspec(path)) { @@ -618,6 +645,7 @@ pub fn diff(request: GitDiffRequest) -> Result { }) } +/// Applies a validated patch using the requested index or working-tree mode. pub fn apply(request: GitApplyRequest) -> Result { let arguments = match request.mode.as_str() { "stage" => vec![ @@ -673,6 +701,7 @@ pub fn apply(request: GitApplyRequest) -> Result }) } +/// Returns bounded commit history without relying on localized display output. pub fn history(request: GitHistoryRequest) -> Result { let limit = request.limit.clamp(1, 5_000); let root = validate_root(&request.root)?; @@ -746,6 +775,7 @@ pub fn history(request: GitHistoryRequest) -> Result Result { let root = validate_root(&request.root)?; validate_revision(&request.commit)?; @@ -774,6 +804,7 @@ pub fn commit(request: GitCommitRequest) -> Result Result { let root = validate_root(&request.root)?; validate_revision(&request.commit)?; @@ -801,6 +832,7 @@ pub fn commit_files(request: GitCommitFilesRequest) -> Result Result { let root = validate_root(&request.root)?; validate_revision(&request.reference)?; @@ -1393,6 +1425,7 @@ fn is_conflicted_status(code: &str) -> bool { matches!(code, "UU" | "AA" | "DD" | "DU" | "UD" | "AU" | "UA") } +/// Lists stashes with stable references and parsed metadata. pub fn stashes(request: GitStashesRequest) -> Result { let root = validate_root(&request.root)?; let response = readonly_command(GitCommandRequest { @@ -1416,6 +1449,7 @@ pub fn stashes(request: GitStashesRequest) -> Result Result { if !is_safe_pathspec(&request.path) { return Err(CoreError::new( @@ -2055,11 +2089,13 @@ fn null_device() -> &'static str { } } +/// One removed or added line retained with its original side's line number. struct DiffEntry { number: usize, text: String, } +/// Parsed patch hunk before it is aligned into side-by-side rows. struct DiffHunkRecord { id: String, header: String, @@ -2364,6 +2400,7 @@ fn parse_diff(patch: &str) -> (Vec, Vec (rows, hunks) } +/// Resolves the Git administrative paths and references a watcher must observe. pub fn watch_context( request: GitWatchContextRequest, ) -> Result, CoreError> { @@ -2415,6 +2452,7 @@ fn canonical_git_output(output: std::process::Output, label: &str) -> Result Result { let root = PathBuf::from(&request.root) .canonicalize() diff --git a/rust/lithe-core/src/languages/java.rs b/rust/lithe-core/src/languages/java.rs index aa0eb769..bcbe485c 100644 --- a/rust/lithe-core/src/languages/java.rs +++ b/rust/lithe-core/src/languages/java.rs @@ -1,3 +1,5 @@ +//! Lightweight Java source and Maven-aware run-configuration inspection. + use crate::protocol::{CoreError, ErrorCode}; use crate::protocol::{ JavaClassNameResponse, JavaCodeVisionHintResponse, JavaCodeVisionResponse, @@ -13,6 +15,7 @@ use std::path::{Component, Path, PathBuf}; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Workspace inputs used to discover Java main classes and run configurations. pub struct JavaRunConfigurationsRequest { pub root: String, #[serde(default)] @@ -23,6 +26,7 @@ pub struct JavaRunConfigurationsRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Source inputs for lightweight folds, markers, and inlay hints. pub struct JavaStructureRequest { pub source: String, #[serde(default)] @@ -31,6 +35,7 @@ pub struct JavaStructureRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Workspace sources used to count references for Code Vision hints. pub struct JavaCodeVisionRequest { pub root: String, pub target_path: String, @@ -40,6 +45,7 @@ pub struct JavaCodeVisionRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Source and simple name used to resolve a package-qualified class name. pub struct JavaClassNameRequest { pub source: String, pub simple_name: String, @@ -47,6 +53,7 @@ pub struct JavaClassNameRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Source lookup for a type, method, or field declaration. pub struct JavaSourceDefinitionRequest { pub source: String, pub declaration_name: String, @@ -56,11 +63,13 @@ pub struct JavaSourceDefinitionRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Spring configuration content inspected for a declared server port. pub struct JavaServerPortRequest { pub content: String, pub file_extension: String, } +/// Discovers stable Java and Spring Boot run entries from selected sources. pub fn run_configurations( request: JavaRunConfigurationsRequest, ) -> Result { @@ -115,6 +124,7 @@ pub fn run_configurations( }) } +/// Computes lightweight Java structure without starting a language server. pub fn structure(request: JavaStructureRequest) -> Result { let source = request.source; Ok(JavaStructureResponse { @@ -124,6 +134,7 @@ pub fn structure(request: JavaStructureRequest) -> Result Result { let root = existing_root(&request.root)?; let target_path = normalize_relative(&request.target_path).ok_or_else(|| { @@ -169,6 +180,7 @@ pub fn code_vision(request: JavaCodeVisionRequest) -> Result Result { let package = Regex::new(r"(?m)^\s*package\s+([A-Za-z_][A-Za-z0-9_.]*)\s*;") .expect("static Java package expression is valid") @@ -181,6 +193,7 @@ pub fn class_name(request: JavaClassNameRequest) -> Result Result, CoreError> { @@ -244,6 +257,7 @@ pub fn source_definition( Ok(None) } +/// Reads a Spring server port from properties or YAML content. pub fn server_port(request: JavaServerPortRequest) -> Result { if request.file_extension.eq_ignore_ascii_case("properties") { for line in request.content.lines() { @@ -838,6 +852,7 @@ fn utf16_offset(source: &str, byte: usize) -> usize { source[..byte.min(source.len())].encode_utf16().count() } +/// Lexical region used to exclude strings, characters, and comments from scans. enum ScanState { Code, String, diff --git a/rust/lithe-core/src/lib.rs b/rust/lithe-core/src/lib.rs index b28fcba3..c8110a06 100644 --- a/rust/lithe-core/src/lib.rs +++ b/rust/lithe-core/src/lib.rs @@ -1,3 +1,5 @@ +//! Deterministic application services shared by the macOS and Windows hosts. + mod execution; mod git; mod languages; diff --git a/rust/lithe-core/src/lsp/interface/client.rs b/rust/lithe-core/src/lsp/interface/client.rs index d67963e1..80ef4b63 100644 --- a/rust/lithe-core/src/lsp/interface/client.rs +++ b/rust/lithe-core/src/lsp/interface/client.rs @@ -1,7 +1,10 @@ +//! Pure LSP client-state transitions and JSON-RPC message construction. + use super::types::*; use crate::protocol::{CoreError, ErrorCode}; use serde_json::{json, Value}; +/// Creates an initialize request and records it as pending client state. pub fn client_initialize(request: ClientInitializeRequest) -> Result { validate_uri(&request.root_uri)?; let workspace_name = workspace_name_from_uri(&request.root_uri); @@ -109,6 +112,7 @@ pub fn client_initialize(request: ClientInitializeRequest) -> Result Result { @@ -137,6 +141,7 @@ pub fn client_open_document( Ok(client_response(state, vec![message], Vec::new())) } +/// Replaces a document and emits a monotonically versioned change notification. pub fn client_change_document( request: ClientChangeDocumentRequest, ) -> Result { @@ -165,6 +170,7 @@ pub fn client_change_document( Ok(client_response(state, vec![message], Vec::new())) } +/// Closes a document and clears diagnostics owned by its URI. pub fn client_close_document( request: ClientCloseDocumentRequest, ) -> Result { @@ -189,6 +195,7 @@ pub fn client_close_document( Ok(client_response(state, vec![message], Vec::new())) } +/// Begins the two-step LSP shutdown and exit handshake. pub fn client_shutdown(request: ClientShutdownRequest) -> Result { let mut state = request.state; if state.shutdown_requested { @@ -223,6 +230,7 @@ pub(crate) fn client_feature_request_canonical( Ok(client_response(state, vec![message], Vec::new())) } +/// Reduces one server JSON-RPC message into state changes and host events. pub fn client_apply_server_message( request: ClientApplyServerMessageRequest, ) -> Result { diff --git a/rust/lithe-core/src/lsp/interface/engine.rs b/rust/lithe-core/src/lsp/interface/engine.rs index 7972a68d..aa3aa96b 100644 --- a/rust/lithe-core/src/lsp/interface/engine.rs +++ b/rust/lithe-core/src/lsp/interface/engine.rs @@ -1,3 +1,5 @@ +//! Stateful language-server sessions coordinating client state and child processes. + use super::process::{LspProcessHandle, LspProcessLauncher, LspProcessSpec, SystemProcessLauncher}; use super::{ client_apply_server_message, client_change_document, client_close_document, @@ -32,18 +34,27 @@ static ENGINE: OnceLock = OnceLock::new(); #[derive(Debug, Clone, Copy, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +/// Observable lifecycle of one managed language-server session. pub enum LspLifecycleState { + /// Session state exists, but the child process has not started. Created, + /// The child process and its standard streams are being created. ProcessStarting, + /// The process is running and the initialize handshake is pending. Initializing, + /// Initialization completed and semantic requests may be sent. Ready, + /// A graceful shutdown is pending or the process is being terminated. Stopping, + /// The session ended normally and will produce no further events. Stopped, + /// Startup, protocol handling, or the child process failed terminally. Failed, } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] +/// Validated process, workspace, initialization, and timeout settings for a server. pub struct StartServerRequest { pub provider_id: String, pub executable_path: String, @@ -69,6 +80,7 @@ pub struct StartServerRequest { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Identity and initial lifecycle state of a newly created session. pub struct StartServerResponse { pub session_id: String, pub state: LspLifecycleState, @@ -77,12 +89,14 @@ pub struct StartServerResponse { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] +/// Request targeting one existing server session. pub struct SessionRequest { pub session_id: String, } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] +/// Complete document contents to open or update in a session. pub struct SyncDocumentRequest { pub session_id: String, pub uri: String, @@ -92,6 +106,7 @@ pub struct SyncDocumentRequest { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] +/// Request to remove a document from a session's synchronized state. pub struct CloseDocumentRequest { pub session_id: String, pub uri: String, @@ -99,28 +114,47 @@ pub struct CloseDocumentRequest { #[derive(Debug, Clone, Copy, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +/// Semantic operation normalized across provider-specific LSP capabilities. pub enum LspSemanticOperation { + /// `textDocument/completion`. Completion, + /// `textDocument/hover`. Hover, + /// `textDocument/definition`. Definition, + /// `textDocument/declaration`. Declaration, + /// `textDocument/typeDefinition`. TypeDefinition, + /// `textDocument/references`. References, + /// `textDocument/implementation`. Implementation, + /// `textDocument/rename`. Rename, + /// `textDocument/formatting`. Formatting, + /// `textDocument/codeAction`. CodeActions, + /// `completionItem/resolve` for a previously returned completion item. ResolveCompletion, + /// `codeAction/resolve` for a previously returned action. ResolveCodeAction, + /// `workspace/executeCommand` using a server-provided command payload. ExecuteCommand, + /// `textDocument/inlayHint`. InlayHints, + /// `textDocument/foldingRange`. FoldingRanges, + /// `textDocument/codeLens`. CodeLens, + /// Provider-specific retrieval of a read-only virtual document. VirtualDocument, } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] +/// Inputs for one asynchronous semantic language-server operation. pub struct SemanticRequest { pub session_id: String, #[serde(default)] @@ -148,12 +182,14 @@ pub struct SemanticRequest { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Correlation identifier used to receive or cancel an asynchronous result. pub struct OperationResponse { pub operation_id: String, } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] +/// Request to cancel a pending operation in one session. pub struct CancelOperationRequest { pub session_id: String, pub operation_id: String, @@ -161,13 +197,16 @@ pub struct CancelOperationRequest { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Ordered events drained from a session since the previous poll. pub struct PollEventsResponse { pub events: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Sequenced lifecycle, diagnostic, result, or log event from a server session. pub struct LspRuntimeEvent { + /// Event discriminator such as `stateChanged`, `requestCompleted`, or `diagnostics`. #[serde(rename = "type")] pub kind: String, pub sequence: u64, @@ -203,6 +242,7 @@ pub struct LspRuntimeEvent { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Server identity reported by the LSP initialize response. pub struct LspServerInfo { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -211,10 +251,12 @@ pub struct LspServerInfo { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Structured runtime failure with session and protocol-stage context. pub struct LspRuntimeError { pub code: String, pub provider_id: String, pub session_id: String, + /// Lifecycle or protocol phase in which the error occurred. pub stage: String, #[serde(skip_serializing_if = "Option::is_none")] pub method: Option, @@ -245,10 +287,15 @@ pub struct EngineSnapshot { } #[derive(Debug, Clone, Copy, Eq, PartialEq)] +/// Response handling path required by one pending JSON-RPC request. enum PendingKind { + /// Initial handshake whose result transitions the session to ready. Initialize, + /// Ordinary semantic feature whose result becomes an operation event. Feature, + /// Provider-specific source retrieval normalized as a virtual document. VirtualDocument, + /// Shutdown handshake after which the engine sends `exit`. Shutdown, } @@ -308,26 +355,31 @@ fn default_shutdown_timeout() -> u64 { DEFAULT_SHUTDOWN_TIMEOUT_MS } +/// Starts a managed language-server process and begins LSP initialization. pub fn start_server(request: StartServerRequest) -> Result { engine().start_server(request) } +/// Performs a bounded graceful shutdown while retaining the session record. pub fn stop_server(request: SessionRequest) -> Result<(), CoreError> { engine().session(&request.session_id)?.stop() } +/// Opens or replaces the synchronized contents of one document. pub fn sync_document(request: SyncDocumentRequest) -> Result<(), CoreError> { engine() .session(&request.session_id)? .sync_document(request) } +/// Notifies the server that a synchronized document has closed. pub fn close_document(request: CloseDocumentRequest) -> Result<(), CoreError> { engine() .session(&request.session_id)? .close_document(&request.uri) } +/// Queues a semantic request and returns its operation identifier immediately. pub fn semantic_request(request: SemanticRequest) -> Result { let operation_id = request .operation_id @@ -339,18 +391,21 @@ pub fn semantic_request(request: SemanticRequest) -> Result Result<(), CoreError> { engine() .session(&request.session_id)? .cancel_operation(&request.operation_id) } +/// Drains all currently queued events in deterministic sequence order. pub fn poll_events(request: SessionRequest) -> Result { Ok(PollEventsResponse { events: engine().session(&request.session_id)?.poll_events()?, }) } +/// Stops a session if necessary and removes all state owned by it. pub fn destroy_server(request: SessionRequest) -> Result<(), CoreError> { engine().destroy(&request.session_id) } diff --git a/rust/lithe-core/src/lsp/interface/process.rs b/rust/lithe-core/src/lsp/interface/process.rs index 231c8754..2f59aa1d 100644 --- a/rust/lithe-core/src/lsp/interface/process.rs +++ b/rust/lithe-core/src/lsp/interface/process.rs @@ -56,7 +56,9 @@ pub struct LspProcessStreams { pub errors: Box, } +/// Factory boundary used by the engine to start real or scripted servers. pub trait LspProcessLauncher: Send + Sync { + /// Starts one process and transfers ownership of its handle and output streams. fn launch(&self, spec: LspProcessSpec) -> Result; } diff --git a/rust/lithe-core/src/lsp/interface/transport.rs b/rust/lithe-core/src/lsp/interface/transport.rs index 8d8b873f..e6ce3d30 100644 --- a/rust/lithe-core/src/lsp/interface/transport.rs +++ b/rust/lithe-core/src/lsp/interface/transport.rs @@ -1,3 +1,5 @@ +//! Bounded encoding and incremental parsing of LSP transport frames. + use crate::protocol::{CoreError, ErrorCode}; use serde::{Deserialize, Serialize}; @@ -6,18 +8,21 @@ const MAX_MESSAGE_BYTES: usize = 64 * 1024 * 1024; #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// JSON-RPC message to encode with the LSP content-length framing protocol. pub struct FrameMessageRequest { pub message: String, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Complete transport frame ready to write to a language server. pub struct FrameMessageResponse { pub frame: String, } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Previously buffered bytes and the newest process-output chunk. pub struct ParseServerMessagesRequest { #[serde(default)] pub buffer: Vec, @@ -27,10 +32,12 @@ pub struct ParseServerMessagesRequest { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Complete JSON-RPC messages plus the unconsumed partial frame. pub struct ParseServerMessagesResponse { pub buffer: Vec, pub messages: Vec, } +/// Encodes one JSON-RPC message with its UTF-8 byte length. pub fn frame_message(request: FrameMessageRequest) -> Result { if request.message.contains('\0') { return Err(CoreError::new( @@ -47,6 +54,7 @@ pub fn frame_message(request: FrameMessageRequest) -> Result Result { diff --git a/rust/lithe-core/src/lsp/interface/types.rs b/rust/lithe-core/src/lsp/interface/types.rs index 5ecc17c0..493862e8 100644 --- a/rust/lithe-core/src/lsp/interface/types.rs +++ b/rust/lithe-core/src/lsp/interface/types.rs @@ -1,9 +1,12 @@ +//! Serializable client state and wire models for the generic LSP implementation. + use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::BTreeMap; #[derive(Debug, Clone, Copy, Deserialize)] #[serde(rename_all = "camelCase")] +/// Half-open document range expressed in zero-based LSP coordinates. pub struct LspRange { pub start: LspPosition, pub end: LspPosition, @@ -11,6 +14,7 @@ pub struct LspRange { #[derive(Debug, Clone, Copy, Deserialize)] #[serde(rename_all = "camelCase")] +/// Zero-based LSP line and UTF-16 code-unit column. pub struct LspPosition { pub line: i64, pub utf16_column: i64, @@ -18,6 +22,7 @@ pub struct LspPosition { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Normalized text replacement returned to host applications. pub struct LspTextEditResponse { pub range: LspRangeResponse, pub new_text: String, @@ -25,9 +30,11 @@ pub struct LspTextEditResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Normalized inlay hint independent of provider-specific extensions. pub struct LspInlayHintResponse { pub position: LspPositionResponse, pub label: String, + /// Numeric LSP `InlayHintKind`, when supplied by the server. pub kind: Option, pub tooltip: Option, pub padding_left: bool, @@ -38,17 +45,20 @@ pub struct LspInlayHintResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Normalized folding range using UTF-16 columns when supplied by the server. pub struct LspFoldingRangeResponse { pub start_line: i64, pub start_utf16_column: Option, pub end_line: i64, pub end_utf16_column: Option, + /// Server-provided LSP fold category such as `comment`, `imports`, or `region`. pub kind: Option, pub collapsed_text: Option, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Normalized CodeLens payload awaiting an optional resolve operation. pub struct LspCodeLensResponse { pub range: LspRangeResponse, pub command: Option, @@ -57,6 +67,7 @@ pub struct LspCodeLensResponse { #[derive(Debug, Clone, Copy, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] +/// Serializable form of an LSP document range. pub struct LspRangeResponse { pub start: LspPositionResponse, pub end: LspPositionResponse, @@ -64,6 +75,7 @@ pub struct LspRangeResponse { #[derive(Debug, Clone, Copy, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] +/// Serializable form of a zero-based LSP position. pub struct LspPositionResponse { pub line: i64, pub utf16_column: i64, @@ -71,6 +83,7 @@ pub struct LspPositionResponse { #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] +/// Pure client protocol state carried between JSON command invocations. pub struct LspClientState { #[serde(default = "default_next_request_id")] pub next_request_id: u64, @@ -111,6 +124,7 @@ fn default_next_request_id() -> u64 { #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] +/// Document version and contents currently synchronized with the server. pub struct LspClientDocument { pub uri: String, pub language_id: String, @@ -120,6 +134,7 @@ pub struct LspClientDocument { #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] +/// Diagnostic normalized to fields supported by every frontend. pub struct LspClientDiagnostic { pub range: LspRangeResponse, pub severity: Option, @@ -134,6 +149,7 @@ pub struct LspClientDiagnostic { #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] +/// Related diagnostic message and its source location. pub struct LspClientDiagnosticRelatedInformation { pub location: LspClientDiagnosticLocation, pub message: String, @@ -141,6 +157,7 @@ pub struct LspClientDiagnosticRelatedInformation { #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] +/// URI and range referenced by related diagnostic information. pub struct LspClientDiagnosticLocation { pub uri: String, pub range: LspRangeResponse, @@ -148,6 +165,7 @@ pub struct LspClientDiagnosticLocation { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Inputs for constructing the LSP initialize request and initial client state. pub struct ClientInitializeRequest { #[serde(default)] pub state: LspClientState, @@ -160,6 +178,7 @@ pub struct ClientInitializeRequest { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Inputs for opening a complete document in pure client state. pub struct ClientOpenDocumentRequest { #[serde(default)] pub state: LspClientState, @@ -170,6 +189,7 @@ pub struct ClientOpenDocumentRequest { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Inputs for replacing a synchronized document's complete contents. pub struct ClientChangeDocumentRequest { #[serde(default)] pub state: LspClientState, @@ -179,6 +199,7 @@ pub struct ClientChangeDocumentRequest { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Inputs for closing a document in pure client state. pub struct ClientCloseDocumentRequest { #[serde(default)] pub state: LspClientState, @@ -187,6 +208,7 @@ pub struct ClientCloseDocumentRequest { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Inputs for the LSP shutdown handshake. pub struct ClientShutdownRequest { #[serde(default)] pub state: LspClientState, @@ -194,6 +216,7 @@ pub struct ClientShutdownRequest { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Generic feature request translated into one provider-neutral JSON-RPC call. pub struct ClientFeatureRequest { #[serde(default)] pub state: LspClientState, @@ -217,6 +240,7 @@ pub struct ClientFeatureRequest { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Server JSON-RPC message to reduce into the current client state. pub struct ClientApplyServerMessageRequest { #[serde(default)] pub state: LspClientState, @@ -224,6 +248,7 @@ pub struct ClientApplyServerMessageRequest { } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Updated client state, outbound messages, and host-facing events. pub struct LspClientResponse { pub state: LspClientState, pub messages: Vec, @@ -232,7 +257,9 @@ pub struct LspClientResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Normalized event produced while reducing a server message. pub struct LspClientEvent { + /// Pure-client event category: `diagnostics`, `notification`, `response`, or `error`. pub kind: String, #[serde(skip_serializing_if = "Option::is_none")] pub request_id: Option, diff --git a/rust/lithe-core/src/lsp/languages/catalog.rs b/rust/lithe-core/src/lsp/languages/catalog.rs index b2c2274b..7f3bad89 100644 --- a/rust/lithe-core/src/lsp/languages/catalog.rs +++ b/rust/lithe-core/src/lsp/languages/catalog.rs @@ -1,3 +1,5 @@ +//! Loading and validation for built-in and workspace language-provider catalogs. + use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::BTreeMap; @@ -10,6 +12,7 @@ const BUILTIN_LANGUAGE_PROVIDERS: &str = include_str!(concat!( #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Effective provider catalog after applying an optional workspace override. pub struct LspProviderCatalog { pub version: u32, pub origin: LspProviderCatalogOrigin, @@ -20,13 +23,17 @@ pub struct LspProviderCatalog { #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +/// Source that last changed the effective provider configuration. pub enum LspProviderCatalogOrigin { + /// The effective catalog is exactly the embedded provider document. Builtin, + /// At least one workspace-local patch was merged into the built-ins. WorkspaceOverride, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Non-fatal configuration problem surfaced alongside usable providers. pub struct LspProviderConfigDiagnostic { pub path: String, pub message: String, @@ -34,6 +41,7 @@ pub struct LspProviderConfigDiagnostic { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// File recognition, capabilities, and server metadata for one language provider. pub struct LspProviderDescriptor { pub id: String, pub display_name: String, @@ -51,6 +59,7 @@ pub struct LspProviderDescriptor { #[derive(Debug, Clone, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] +/// Executable candidates and initialization values for a language server. pub struct LspServerLaunchDescriptor { pub executable_names: Vec, #[serde(default)] @@ -65,6 +74,7 @@ pub struct LspServerLaunchDescriptor { #[derive(Debug, Clone, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] +/// Installation hints shown when no compatible server executable is available. pub struct LspServerInstallationDescriptor { #[serde(default)] pub homebrew_formula: Option, @@ -74,18 +84,27 @@ pub struct LspServerInstallationDescriptor { #[derive(Debug, Clone, Copy, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +/// Product feature that a language provider can contribute. pub enum LspProviderCapability { + /// Produces runnable project configurations. Run, + /// Supplies semantic language features through an LSP process. LanguageServer, + /// Supplies an adapter that implements the Debug Adapter Protocol. DebugAdapter, + /// Formats source documents. Formatting, + /// Discovers or runs project tests. Testing, } #[derive(Debug, Clone, Copy, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +/// Policy controlling when a provider's external process may start. pub enum LspActivationPolicy { + /// Starts the provider only after a feature explicitly requests it. OnDemand, + /// Starts the provider as soon as its project activation conditions match. Always, } @@ -135,6 +154,7 @@ struct LspProviderPatch { #[serde(default)] disabled: bool, } +/// Serializes the effective provider catalog for the C ABI. pub fn provider_catalog_json(workspace_root: Option<&Path>) -> String { let catalog = provider_catalog(workspace_root); serde_json::to_string(&catalog).unwrap_or_else(|_| { @@ -147,6 +167,7 @@ pub fn provider_catalog_json(workspace_root: Option<&Path>) -> String { }) } +/// Loads built-ins and applies a workspace-local provider configuration, if present. pub fn provider_catalog(workspace_root: Option<&Path>) -> LspProviderCatalog { let mut diagnostics = Vec::new(); let mut origin = LspProviderCatalogOrigin::Builtin; diff --git a/rust/lithe-core/src/lsp/languages/swift.rs b/rust/lithe-core/src/lsp/languages/swift.rs index 5bbdd607..5a8b7db6 100644 --- a/rust/lithe-core/src/lsp/languages/swift.rs +++ b/rust/lithe-core/src/lsp/languages/swift.rs @@ -1,3 +1,5 @@ +//! Translation between SourceKit-LSP extensions and the generic LSP model. + use crate::lsp::interface::ClientFeatureRequest; use serde_json::{json, Value}; diff --git a/rust/lithe-core/src/lsp/lightweight/edits.rs b/rust/lithe-core/src/lsp/lightweight/edits.rs index 6dda78d6..f9e9d4af 100644 --- a/rust/lithe-core/src/lsp/lightweight/edits.rs +++ b/rust/lithe-core/src/lsp/lightweight/edits.rs @@ -1,9 +1,12 @@ +//! UTF-16-aware text edit validation and application. + use crate::lsp::interface::{LspPosition, LspPositionResponse, LspRange, LspRangeResponse}; use crate::protocol::{CoreError, ErrorCode}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Source text and LSP edits to validate and apply as one operation. pub struct ApplyTextEditsRequest { pub text: String, #[serde(default)] @@ -12,6 +15,7 @@ pub struct ApplyTextEditsRequest { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Replacement expressed in LSP UTF-16 coordinates. pub struct LspTextEdit { pub range: LspRange, pub new_text: String, @@ -19,10 +23,12 @@ pub struct LspTextEdit { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Text-only response shared by edit and snippet commands. pub struct TextResponse { pub text: String, } +/// Applies non-overlapping edits from the end of the document toward the start. pub fn apply_text_edits(request: ApplyTextEditsRequest) -> Result { let mut replacements = Vec::new(); for edit in request.edits { diff --git a/rust/lithe-core/src/lsp/lightweight/snippets.rs b/rust/lithe-core/src/lsp/lightweight/snippets.rs index 92f3c89d..4da97965 100644 --- a/rust/lithe-core/src/lsp/lightweight/snippets.rs +++ b/rust/lithe-core/src/lsp/lightweight/snippets.rs @@ -1,12 +1,16 @@ +//! Conversion of LSP snippets into insertion-ready plain text. + use super::edits::TextResponse; use serde::Deserialize; #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// LSP snippet whose placeholders should be reduced to insertion text. pub struct PlainSnippetRequest { pub value: String, } +/// Removes snippet control syntax while preserving default placeholder text. pub fn plain_snippet(request: PlainSnippetRequest) -> TextResponse { TextResponse { text: snippet_plain_text(&request.value), diff --git a/rust/lithe-core/src/lsp/lightweight/symbols.rs b/rust/lithe-core/src/lsp/lightweight/symbols.rs index 0d1c00c0..ccd9736d 100644 --- a/rust/lithe-core/src/lsp/lightweight/symbols.rs +++ b/rust/lithe-core/src/lsp/lightweight/symbols.rs @@ -1,3 +1,5 @@ +//! In-process document symbols, references, renames, and semantic-token helpers. + use super::edits::{range_for_offsets, utf16_position_to_byte_offset}; use crate::lsp::interface::{ LspPosition, LspPositionResponse, LspRangeResponse, LspTextEditResponse, @@ -8,6 +10,7 @@ use std::collections::BTreeMap; #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Document and cursor used by lightweight completion or hover. pub struct BuiltinRequest { pub file_path: String, pub text: String, @@ -16,6 +19,7 @@ pub struct BuiltinRequest { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Document, cursor, and LSP method used by lightweight navigation. pub struct BuiltinNavigationRequest { pub file_path: String, pub text: String, @@ -25,15 +29,18 @@ pub struct BuiltinNavigationRequest { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Deterministically ordered completion candidates from the current document. pub struct BuiltinCompletionResponse { pub items: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Completion candidate expressed in the normalized Core response shape. pub struct BuiltinCompletionItem { pub label: String, pub insert_text: String, + /// Numeric LSP `CompletionItemKind`, when the fallback can infer one. pub kind: Option, pub detail: Option, pub text_edit: LspTextEditResponse, @@ -41,12 +48,14 @@ pub struct BuiltinCompletionItem { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Optional hover result for the identifier at the cursor. pub struct BuiltinHoverResponse { pub hover: Option, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Lightweight hover contents and the identifier range they describe. pub struct BuiltinHover { pub contents: String, pub is_markdown: bool, @@ -55,12 +64,14 @@ pub struct BuiltinHover { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Locations found by the lightweight navigation fallback. pub struct BuiltinNavigationResponse { pub locations: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// One normalized navigation target. pub struct BuiltinLocation { pub file_path: String, pub range: LspRangeResponse, @@ -76,6 +87,7 @@ struct IdentifierOccurrence { range: LspRangeResponse, } +/// Produces prefix-matching identifiers from the current document. pub fn builtin_completions( request: BuiltinRequest, ) -> Result { @@ -127,6 +139,7 @@ pub fn builtin_completions( Ok(BuiltinCompletionResponse { items }) } +/// Returns a minimal hover for the identifier under the cursor. pub fn builtin_hover(request: BuiltinRequest) -> Result { validate_file_path(&request.file_path)?; let cursor = utf16_position_to_byte_offset(&request.text, request.position)?; @@ -142,6 +155,7 @@ pub fn builtin_hover(request: BuiltinRequest) -> Result Result { diff --git a/rust/lithe-core/src/plugins/mod.rs b/rust/lithe-core/src/plugins/mod.rs index 276b7926..a150f36e 100644 --- a/rust/lithe-core/src/plugins/mod.rs +++ b/rust/lithe-core/src/plugins/mod.rs @@ -1,17 +1,27 @@ +//! Plugin manifest parsing, compatibility checks, and deterministic catalog merging. + use serde::Deserialize; use std::collections::{BTreeMap, BTreeSet}; +/// Manifest schema understood by this Core build. pub const PLUGIN_MANIFEST_SCHEMA_VERSION: u32 = 1; +/// Host/plugin API level required by compatible packages. pub const PLUGIN_API_VERSION: u32 = 1; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +/// Strict three-component semantic version used for compatibility comparisons. pub struct PluginVersion { + /// Breaking-change component. pub major: u32, + /// Backward-compatible feature component. pub minor: u32, + /// Backward-compatible fix component. pub patch: u32, } impl PluginVersion { + /// Parses exactly `major.minor.patch`; prerelease tags and missing parts are + /// rejected because the manifest contract does not define their ordering. pub fn parse(value: &str) -> Option { let mut parts = value.split('.'); let version = Self { @@ -24,93 +34,178 @@ impl PluginVersion { } #[derive(Debug, Clone, PartialEq, Eq)] +/// Deterministic reason a catalog cannot be loaded by the current host. pub enum PluginValidationError { + /// The catalog is not valid JSON or does not match the manifest shape. InvalidJson, - UnsupportedSchema { plugin: String, version: u32 }, - UnsupportedApi { plugin: String, version: u32 }, - InvalidVersion { plugin: String, value: String }, - IncompatibleHost { plugin: String }, - InvalidEntrypoint { plugin: String }, + /// A catalog or plugin uses an unknown manifest schema. + UnsupportedSchema { + /// Plugin identifier, or `catalog` when the top-level schema failed. + plugin: String, + /// Unsupported manifest schema version found in the input. + version: u32, + }, + /// A catalog or plugin targets a different plugin API. + UnsupportedApi { + /// Plugin identifier, or `catalog` when the top-level API failed. + plugin: String, + /// Unsupported plugin API level found in the input. + version: u32, + }, + /// A version does not use the strict three-component format. + InvalidVersion { + /// Plugin whose version or compatibility bound is malformed. + plugin: String, + /// Original version string that could not be parsed. + value: String, + }, + /// The current host falls outside the package's declared version interval. + IncompatibleHost { + /// Plugin identifier, or `catalog` for a fixture-host mismatch. + plugin: String, + }, + /// Entrypoint metadata is incomplete or inconsistent with its kind. + InvalidEntrypoint { + /// Plugin containing inconsistent loading or publisher metadata. + plugin: String, + }, + /// More than one package declares the same plugin identifier. DuplicatePlugin(String), + /// More than one package claims ownership of the same module identifier. DuplicateModule(String), + /// A plugin contains no modules and therefore cannot contribute behavior. EmptyPlugin(String), + /// Plugin packages are not in canonical identifier order. UnsortedPlugins, - UnsortedModules { plugin: String }, - InvalidLanguageSupport { plugin: String, language: String }, + /// A package's module identifiers are not in canonical order. + UnsortedModules { + /// Plugin whose module identifiers are not in canonical order. + plugin: String, + }, + /// Language recognition or capability ownership is invalid. + InvalidLanguageSupport { + /// Plugin declaring the invalid language contribution. + plugin: String, + /// Language identifier whose recognition or module ownership is invalid. + language: String, + }, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Top-level plugin catalog fixture consumed by compatibility verification. pub struct PluginCatalogFixture { + /// Version of the catalog JSON shape. pub schema_version: u32, + /// Exact host version for which the fixture was assembled. pub host_version: String, + /// Plugin API level shared by every package in the catalog. #[serde(rename = "pluginAPIVersion")] pub plugin_api_version: u32, + /// Packages sorted by stable plugin identifier. pub plugins: Vec, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Compatibility and ownership metadata for one plugin package. pub struct PluginPackageManifest { + /// Stable package identifier used as the catalog key. pub id: String, + /// Human-readable name presented by host applications. pub display_name: String, + /// Package version in strict `major.minor.patch` form. pub version: String, + /// Plugin API level against which the package was built. pub api_version: u32, + /// Inclusive lower and optional exclusive upper host bounds. pub host_compatibility: HostCompatibility, + /// Publisher identity and signature policy. pub vendor: PluginVendor, + /// Native or built-in loading metadata. pub entrypoint: PluginEntrypoint, + /// Stable module identifiers owned by this package, in sorted order. #[serde(rename = "moduleIDs")] pub module_ids: Vec, + /// Language capabilities contributed by the package. #[serde(default)] pub language_supports: Vec, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// File recognition and module ownership for one contributed language. pub struct LanguageSupportManifest { + /// Lowercase stable language identifier. pub id: String, + /// Human-readable language name. pub display_name: String, + /// Extensions without a leading dot, kept in deterministic order. #[serde(default)] pub file_extensions: Vec, + /// Exact file names recognized as this language. #[serde(default)] pub file_names: Vec, + /// Project marker names that activate language support for a workspace. #[serde(default)] pub project_file_names: Vec, + /// Package-owned module providing language-server integration. #[serde(rename = "languageServerModuleID")] pub language_server_module_id: Option, + /// Package-owned module providing run configurations. #[serde(rename = "executionModuleID")] pub execution_module_id: Option, + /// Package-owned module providing test integration. #[serde(rename = "testingModuleID")] pub testing_module_id: Option, + /// Package-owned module providing debug integration. #[serde(rename = "debugModuleID")] pub debug_module_id: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Half-open host-version interval supported by a plugin. pub struct HostCompatibility { + /// Oldest compatible host version, inclusive. pub minimum: String, + /// First incompatible host version, when an upper bound is required. pub maximum_exclusive: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Plugin publisher identity and the signature relationship required by the host. pub struct PluginVendor { + /// Stable publisher identifier. pub id: String, + /// Human-readable publisher name. pub display_name: String, + /// Signature policy; currently only `sameTeamAsHost` is accepted. pub signature_requirement: String, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Mutually exclusive loading metadata for built-in and native-bundle plugins. pub struct PluginEntrypoint { + /// Entrypoint discriminator: `builtIn` or `nativeBundle`. pub kind: String, + /// Build target used by a built-in plugin. pub target_name: Option, + /// Bundle identifier required for a native plugin. pub bundle_identifier: Option, + /// Principal class required for a native plugin. pub principal_class: Option, + /// Workspace-relative bundle location required for a native plugin. pub bundle_path: Option, } +/// Validates a complete catalog and returns the owning plugin for every module. +/// +/// Validation also enforces deterministic ordering, host/API compatibility, +/// entrypoint consistency, and that language capabilities reference only +/// modules owned by their declaring package. pub fn validate_plugin_catalog_json( input: &str, host_version: PluginVersion, diff --git a/rust/lithe-core/src/project/files.rs b/rust/lithe-core/src/project/files.rs index 0a971340..3c360eca 100644 --- a/rust/lithe-core/src/project/files.rs +++ b/rust/lithe-core/src/project/files.rs @@ -1,3 +1,5 @@ +//! Workspace traversal, file operations, search, and replacement previews. + use super::search_index::{self, UpdateOutcome, WorkspaceSearchIndex}; use crate::protocol::{invalid_relative_path, CoreError, ErrorCode}; use crate::protocol::{ @@ -32,6 +34,7 @@ const MAX_OPEN_FILE_SIZE: u64 = 32 * 1024 * 1024; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Workspace root and visibility overrides used to build a file-tree snapshot. pub struct WorkspaceSnapshotRequest { pub root: String, #[serde(default)] @@ -42,6 +45,7 @@ pub struct WorkspaceSnapshotRequest { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Bounded file, content, and symbol search options. pub struct SearchRequest { pub root: String, pub query: String, @@ -63,13 +67,14 @@ pub struct SearchRequest { pub hidden_directory_names: Vec, #[serde(default)] pub hidden_file_patterns: Vec, - /// 逗号分隔的文件掩码,如 `*.java, *.kt`。空串表示不过滤。 + /// Comma-separated file masks such as `*.java, *.kt`; empty means no filter. #[serde(default)] pub file_mask: String, } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Workspace root and visibility rules used to warm or invalidate an index. pub struct SearchIndexRequest { pub root: String, #[serde(default)] @@ -80,6 +85,7 @@ pub struct SearchIndexRequest { #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Changed workspace-relative paths to apply to a cached search index. pub struct SearchIndexUpdateRequest { pub root: String, #[serde(default)] @@ -92,6 +98,7 @@ pub struct SearchIndexUpdateRequest { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Search-index size and whether an incremental request required a rebuild. pub struct SearchIndexStatusResponse { pub file_count: usize, pub symbol_count: usize, @@ -101,6 +108,7 @@ pub struct SearchIndexStatusResponse { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Search and replacement options used to produce a non-mutating preview. pub struct ReplacementPreviewRequest { pub root: String, pub query: String, @@ -111,7 +119,7 @@ pub struct ReplacementPreviewRequest { pub whole_words: bool, #[serde(default)] pub regular_expression: bool, - /// 保留原命中的大小写形态:全大写、首字母大写、其余照抄替换串。 + /// Preserve all-uppercase or initial-uppercase shape from literal matches. #[serde(default)] pub preserve_case: bool, #[serde(default)] @@ -128,6 +136,7 @@ pub struct ReplacementPreviewRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to read one validated workspace-relative UTF-8 file. pub struct FileReadRequest { pub root: String, pub path: String, @@ -135,6 +144,7 @@ pub struct FileReadRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to replace one validated workspace-relative UTF-8 file. pub struct FileWriteRequest { pub root: String, pub path: String, @@ -145,6 +155,7 @@ fn default_max_results() -> usize { 200 } +/// Scans visible workspace entries and invalidates any stale search index. pub fn snapshot(request: WorkspaceSnapshotRequest) -> Result { let root = existing_root(&request.root)?; search_index::invalidate_root(&root); @@ -154,6 +165,7 @@ pub fn snapshot(request: WorkspaceSnapshotRequest) -> Result Result { let root = existing_root(&request.root)?; let query = request.query.trim().to_string(); @@ -255,6 +267,7 @@ fn search_with_index( Ok(SearchResponse { matches }) } +/// Combines file, content, and Java-symbol results for Search Everywhere. pub fn search_everywhere(request: SearchRequest) -> Result { let root = existing_root(&request.root)?; let query = request.query.trim().to_string(); @@ -319,6 +332,7 @@ pub fn search_everywhere(request: SearchRequest) -> Result Result { @@ -407,6 +421,7 @@ pub fn replace_preview( Ok(ReplacementPreviewResponse { files }) } +/// Builds or reuses the search index and reports its current size. pub fn warm_search_index( request: SearchIndexRequest, ) -> Result { @@ -425,6 +440,7 @@ pub fn warm_search_index( }) } +/// Applies changed paths to a cached index, rebuilding when its rules changed. pub fn update_search_index( request: SearchIndexUpdateRequest, ) -> Result { @@ -448,6 +464,7 @@ pub fn update_search_index( }) } +/// Drops the cached index for one validated workspace root. pub fn invalidate_search_index(request: SearchIndexRequest) -> Result<(), CoreError> { let requested_root = PathBuf::from(&request.root); let root = search_index::canonicalize_with_missing_components(&requested_root) @@ -456,6 +473,7 @@ pub fn invalidate_search_index(request: SearchIndexRequest) -> Result<(), CoreEr Ok(()) } +/// Reads one bounded, workspace-contained file as UTF-8 text. pub fn read_file(request: FileReadRequest) -> Result { let root = existing_root(&request.root)?; let path = safe_relative_path(&root, &request.path)?; @@ -482,6 +500,7 @@ pub fn read_file(request: FileReadRequest) -> Result Result { let root = existing_root(&request.root)?; let path = writable_relative_path(&root, &request.path)?; @@ -499,6 +518,7 @@ pub fn write_file(request: FileWriteRequest) -> Result, pub(crate) hidden_file_patterns: Vec, @@ -716,9 +736,10 @@ fn normalize(values: Vec) -> Vec { result } -/// 按原命中文本的大小写形态改写替换串,对齐 IDEA 的 Preserve Case: -/// 全大写命中 -> 替换串全大写;首字母大写 -> 替换串首字母大写; -/// 其余形态(含 camelCase、混合大小写)照抄替换串。 +/// Matches IDEA's Preserve Case behavior for literal replacement text. +/// +/// All-uppercase matches uppercase the replacement, initial-uppercase matches +/// capitalize it, and camelCase or mixed-case matches leave it unchanged. fn apply_case_pattern(matched: &str, replacement: &str) -> String { let letters = matched.chars().filter(|value| value.is_alphabetic()); let mut has_lower = false; @@ -730,11 +751,11 @@ fn apply_case_pattern(matched: &str, replacement: &str) -> String { has_upper = true; } } - // 没有字母可参考时无从判断形态,照抄。 + // With no letters there is no case shape to preserve. if !has_lower && !has_upper { return replacement.to_string(); } - // 多于一个字母的全大写才算 SCREAMING_CASE,避免把单字母 "F" 误判。 + // Require multiple letters so a single `F` is not mistaken for SCREAMING_CASE. let letter_count = matched .chars() .filter(|value| value.is_alphabetic()) @@ -756,7 +777,7 @@ fn apply_case_pattern(matched: &str, replacement: &str) -> String { replacement.to_string() } -/// 把 `*.java, *.kt` 这样的掩码串拆成一组模式;空串返回空表示不过滤。 +/// Splits a mask list such as `*.java, *.kt`; an empty list disables filtering. fn parse_file_mask(mask: &str) -> Vec { mask.split(',') .map(|part| part.trim()) @@ -765,7 +786,7 @@ fn parse_file_mask(mask: &str) -> Vec { .collect() } -/// 掩码只针对文件名比对,任一模式命中即通过。 +/// Matches masks against the file name only and accepts any matching pattern. fn file_mask_allows(masks: &[String], path: &str) -> bool { if masks.is_empty() { return true; @@ -805,6 +826,7 @@ fn glob_matches(pattern: &str, value: &str) -> bool { pattern_index == pattern.len() } +/// Literal or regular-expression search semantics compiled for repeated matches. struct Matcher { plain_query: String, regex: Option, @@ -864,8 +886,8 @@ impl Matcher { }) } - /// `preserve_case` 只作用于字面量替换;正则替换保持原样, - /// 因为替换串里可能含 `$1` 之类的捕获引用,改写大小写会破坏语义。 + /// Preserve Case applies only to literal replacements. Regex replacements + /// can contain captures such as `$1`, whose meaning case rewriting breaks. fn replace_with_options( &self, text: &str, diff --git a/rust/lithe-core/src/project/history.rs b/rust/lithe-core/src/project/history.rs index 35fe93ce..c5d55492 100644 --- a/rust/lithe-core/src/project/history.rs +++ b/rust/lithe-core/src/project/history.rs @@ -1,3 +1,5 @@ +//! Versioned local-history snapshots with bounded retention and storage validation. + use crate::protocol::{invalid_relative_path, CoreError, ErrorCode}; use crate::protocol::{HistoryEntriesResponse, HistoryEntryResponse}; use serde::{Deserialize, Serialize}; @@ -12,6 +14,7 @@ const HISTORY_VERSION: u32 = 2; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Snapshot contents, reason, visibility, and retention policy for one record. pub struct HistoryRecordRequest { pub workspace_root: String, pub storage_root: String, @@ -29,6 +32,7 @@ pub struct HistoryRecordRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Optional file filter and visibility policy for listing snapshot metadata. pub struct HistoryEntriesRequest { pub workspace_root: String, pub storage_root: String, @@ -42,6 +46,7 @@ pub struct HistoryEntriesRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Validated storage-relative path of snapshot content to read. pub struct HistoryContentRequest { pub storage_root: String, pub content_path: String, @@ -49,6 +54,7 @@ pub struct HistoryContentRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to move all history metadata after a workspace path relocation. pub struct HistoryRelocateRequest { pub storage_root: String, pub source_path: String, @@ -57,6 +63,7 @@ pub struct HistoryRelocateRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to assign or clear the user label on one snapshot. pub struct HistoryRenameRequest { pub storage_root: String, pub path: String, @@ -67,6 +74,7 @@ pub struct HistoryRenameRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Request to delete one snapshot and its metadata entry. pub struct HistoryDeleteRequest { pub storage_root: String, pub path: String, @@ -87,6 +95,7 @@ struct StoredEntry { label: Option, } +/// Records a snapshot unless the path is hidden, unchanged, or too large. pub fn record(request: HistoryRecordRequest) -> Result, CoreError> { let workspace = existing_root(&request.workspace_root)?; let relative_path = safe_relative_path(&request.path)?; @@ -176,6 +185,7 @@ pub fn record(request: HistoryRecordRequest) -> Result Result { let workspace = existing_root(&request.workspace_root)?; let rules = VisibilityRules::new(request.hidden_directory_names, request.hidden_file_patterns); @@ -214,6 +224,7 @@ pub fn entries(request: HistoryEntriesRequest) -> Result Result { let storage = storage_root(&request.storage_root)?; let relative = safe_relative_path(&request.content_path)?; @@ -221,6 +232,7 @@ pub fn content(request: HistoryContentRequest) -> Result { Ok(String::from_utf8_lossy(&data).into_owned()) } +/// Moves history metadata between workspace-relative paths. pub fn relocate(request: HistoryRelocateRequest) -> Result<(), CoreError> { let storage = storage_root(&request.storage_root)?; let source = safe_relative_path(&request.source_path)?; @@ -250,6 +262,7 @@ pub fn relocate(request: HistoryRelocateRequest) -> Result<(), CoreError> { Ok(()) } +/// Updates the optional user label for one stored snapshot. pub fn rename(request: HistoryRenameRequest) -> Result { let storage = storage_root(&request.storage_root)?; let relative = safe_relative_path(&request.path)?; @@ -275,6 +288,7 @@ pub fn rename(request: HistoryRenameRequest) -> Result Result<(), CoreError> { let storage = storage_root(&request.storage_root)?; let relative = safe_relative_path(&request.path)?; diff --git a/rust/lithe-core/src/project/markdown.rs b/rust/lithe-core/src/project/markdown.rs index 3e533573..8748a55d 100644 --- a/rust/lithe-core/src/project/markdown.rs +++ b/rust/lithe-core/src/project/markdown.rs @@ -1,3 +1,5 @@ +//! Rendering and sanitization for the Markdown dialect shared by every frontend. + use ammonia::Builder; use comrak::{markdown_to_html, Options}; use serde::{Deserialize, Serialize}; @@ -5,12 +7,14 @@ use std::collections::HashSet; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Markdown source to render with the shared dialect. pub struct MarkdownRenderRequest { pub source: String, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] +/// Sanitized HTML safe for embedding in host previews. pub struct MarkdownRenderResponse { pub html: String, } diff --git a/rust/lithe-core/src/project/maven.rs b/rust/lithe-core/src/project/maven.rs index 750e9a9d..34816572 100644 --- a/rust/lithe-core/src/project/maven.rs +++ b/rust/lithe-core/src/project/maven.rs @@ -1,3 +1,5 @@ +//! Maven reactor inspection, profile discovery, and source diagnostics. + use crate::protocol::{CoreError, ErrorCode}; use crate::protocol::{ MavenDiagnosticResponse, MavenDiagnosticsResponse, MavenModuleResponse, MavenProfileResponse, @@ -13,6 +15,7 @@ use std::path::{Component, Path, PathBuf}; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Workspace paths used to locate and inspect the owning Maven reactor. pub struct MavenScanRequest { pub root: String, #[serde(default)] @@ -21,12 +24,14 @@ pub struct MavenScanRequest { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] +/// Maven process output to normalize into workspace diagnostics. pub struct MavenDiagnosticsRequest { pub root: String, pub output: String, } #[derive(Debug, Default)] +/// Parsed POM fields needed by reactor discovery and run-configuration detection. struct Descriptor { group_id: Option, artifact_id: Option, @@ -55,6 +60,7 @@ pub struct DeclaredModule { } impl DeclaredModule { + /// Reports whether this module applies a build plugin directly. pub fn applies_plugin(&self, artifact_id: &str) -> bool { self.plugins.iter().any(|value| value == artifact_id) } @@ -123,6 +129,7 @@ fn collect_modules( } } +/// Reads a Maven reactor and returns its nested module, profile, and identity data. pub fn scan(request: MavenScanRequest) -> Result, CoreError> { let workspace_root = existing_root(&request.root)?; let Some((root, relative_path)) = maven_root(&workspace_root, &request.paths)? else { @@ -223,11 +230,12 @@ pub(crate) fn maven_root( } } +/// Parses Maven compiler output into stable workspace-relative diagnostics. pub fn diagnostics( request: MavenDiagnosticsRequest, ) -> Result { let _ = existing_root(&request.root)?; - let expression = Regex::new(r"\[(ERROR|WARNING)\]\s+(.*?):\[(\d+)(?:,(\d+))?\]\s+(.*)") + let expression = Regex::new(r"\[(ERROR|WARNING)]\s+(.*?):\[(\d+)(?:,(\d+))?]\s+(.*)") .expect("static Maven diagnostic expression is valid"); let mut seen = HashSet::new(); let issues = request diff --git a/rust/lithe-core/src/project/search_index.rs b/rust/lithe-core/src/project/search_index.rs index 1dd87146..a671ee82 100644 --- a/rust/lithe-core/src/project/search_index.rs +++ b/rust/lithe-core/src/project/search_index.rs @@ -1,3 +1,5 @@ +//! Incremental workspace search indexing with exact final-content matching. + use crate::project::files::{java_symbols, read_searchable_text, relative_path, VisibilityRules}; use crate::protocol::{CoreError, ErrorCode, SearchMatch}; use std::collections::{HashMap, HashSet}; @@ -17,6 +19,7 @@ pub(crate) struct WorkspaceSearchIndex { postings: HashMap>, } +/// Searchable file contents and symbols stored under a stable numeric ID. pub(crate) struct IndexedFile { pub(crate) path: String, trigrams: Vec, @@ -24,6 +27,7 @@ pub(crate) struct IndexedFile { } #[derive(Clone)] +/// Normalized Java symbol retained for Search Everywhere results. pub(crate) struct IndexedSymbol { pub(crate) name: String, pub(crate) kind: String, @@ -32,13 +36,18 @@ pub(crate) struct IndexedSymbol { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// Result of attempting to apply watcher paths to an existing index. pub(crate) enum UpdateOutcome { + /// No compatible cached index existed, so there was nothing to update. NotIndexed, + /// Every changed file was applied to the existing index in place. Updated, + /// A directory or visibility change invalidated the index's global file set. RequiresRebuild, } #[derive(Debug, Clone, Copy)] +/// Counts surfaced after building or updating a workspace index. pub(crate) struct SearchIndexStats { pub(crate) file_count: usize, pub(crate) symbol_count: usize, diff --git a/rust/lithe-core/src/protocol/cancellation.rs b/rust/lithe-core/src/protocol/cancellation.rs index 174fbd05..cf962639 100644 --- a/rust/lithe-core/src/protocol/cancellation.rs +++ b/rust/lithe-core/src/protocol/cancellation.rs @@ -1,3 +1,5 @@ +//! Cooperative operation cancellation and per-thread deadline tracking. + use crate::protocol::{CoreError, ErrorCode}; use std::cell::RefCell; use std::collections::HashMap; @@ -6,6 +8,7 @@ use std::sync::{Arc, Mutex, OnceLock}; use std::time::{Duration, Instant}; #[derive(Clone)] +/// Cancellation flag and absolute deadline installed for the current command. struct State { cancelled: Arc, deadline: Option, @@ -17,11 +20,16 @@ thread_local! { static CURRENT: RefCell> = const { RefCell::new(None) }; } +/// Guard that installs cancellation and timeout state for the current thread. +/// +/// Dropping the scope unregisters the operation and restores the previous +/// thread-local state, including when a command exits through an error path. pub struct Scope { operation_id: Option, } impl Scope { + /// Begins a cancellable operation with an optional relative timeout. pub fn begin(operation_id: Option, timeout_milliseconds: Option) -> Self { let cancelled = Arc::new(AtomicBool::new(false)); if let Some(operation_id) = operation_id.as_deref() { diff --git a/rust/lithe-core/src/protocol/command.rs b/rust/lithe-core/src/protocol/command.rs index e4242c70..2e927c88 100644 --- a/rust/lithe-core/src/protocol/command.rs +++ b/rust/lithe-core/src/protocol/command.rs @@ -1,86 +1,161 @@ +//! Versioned command requests and the stable set of dispatcher command names. + use serde::Deserialize; use serde_json::Value; #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] +/// Versioned request envelope accepted by every shared Core entry point. pub struct CoreRequest { + /// Caller-provided correlation identifier copied to the response. #[serde(default)] pub id: Option, + /// Identifier used for cooperative cancellation and stale-result handling. #[serde(default)] pub operation_id: Option, + /// Optional deadline applied by operations that support bounded execution. #[serde(default)] pub timeout_milliseconds: Option, + /// Stable compatibility name resolved by [`CoreCommand::parse`]. pub command: String, + /// Command-specific JSON object; omitted payloads deserialize as JSON null. #[serde(default)] pub payload: Value, } #[derive(Debug, Clone)] +/// Typed form of the stable command names accepted by the dispatcher. +/// +/// Variants are grouped by domain, but their serialized compatibility names +/// live only in [`CoreCommand::parse`] so every host uses one mapping. pub enum CoreCommand { + /// Reports the Core and protocol versions (`core.ping`). Ping, + /// Builds the visible project tree (`workspace.snapshot`). WorkspaceSnapshot, + /// Builds or reuses the workspace search index (`workspace.searchIndex.warm`). WorkspaceSearchIndexWarm, + /// Applies changed paths to the search index (`workspace.searchIndex.update`). WorkspaceSearchIndexUpdate, + /// Drops a workspace's cached search index (`workspace.searchIndex.invalidate`). WorkspaceSearchIndexInvalidate, + /// Searches visible file paths and contents (`workspace.search`). WorkspaceSearch, + /// Searches file paths, contents, and symbols (`workspace.searchEverywhere`). WorkspaceSearchEverywhere, + /// Previews replacements without writing files (`workspace.replacePreview`). WorkspaceReplacePreview, + /// Reads one workspace-relative UTF-8 file (`file.read`). FileRead, + /// Replaces one workspace-relative UTF-8 file (`file.write`). FileWrite, + /// Records one local-history snapshot (`history.record`). HistoryRecord, + /// Lists retained local-history metadata (`history.entries`). HistoryEntries, + /// Reads the contents of one history snapshot (`history.content`). HistoryContent, + /// Moves history after a workspace path relocation (`history.relocate`). HistoryRelocate, + /// Changes the optional label of a history snapshot (`history.rename`). HistoryRename, + /// Deletes one history snapshot (`history.delete`). HistoryDelete, + /// Inspects a declared Maven reactor (`maven.scan`). MavenScan, + /// Normalizes diagnostics from Maven output (`maven.diagnostics`). MavenDiagnostics, + /// Renders and sanitizes shared Markdown (`markdown.render`). MarkdownRender, + /// Applies validated UTF-16 LSP text edits (`lsp.applyTextEdits`). LspApplyTextEdits, + /// Reduces an LSP snippet to insertion text (`lsp.plainSnippet`). LspPlainSnippet, + /// Provides same-document fallback completions (`lsp.builtinCompletions`). LspBuiltinCompletions, + /// Provides a same-document fallback hover (`lsp.builtinHover`). LspBuiltinHover, + /// Provides same-document fallback navigation (`lsp.builtinNavigation`). LspBuiltinNavigation, + /// Starts and initializes a managed language server (`lsp.startServer`). LspStartServer, + /// Gracefully shuts down a managed server (`lsp.stopServer`). LspStopServer, + /// Opens or updates a synchronized document (`lsp.syncDocument`). LspSyncDocument, + /// Closes a synchronized document (`lsp.closeDocument`). LspCloseDocument, + /// Queues one semantic server request (`lsp.request`). LspRequest, + /// Cancels one pending semantic request (`lsp.cancelOperation`). LspCancelOperation, + /// Drains queued session events (`lsp.pollEvents`). LspPollEvents, + /// Stops and removes a server session (`lsp.destroyServer`). LspDestroyServer, + /// Discovers Java main classes and run entries (`java.runConfigurations`). JavaRunConfigurations, + /// Validates layered run-configuration documents (`runConfig.inspect`). RunConfigInspect, + /// Regenerates detected run configurations (`runConfig.generate`). RunConfigGenerate, + /// Merges configuration layers and toolchains (`runConfig.resolve`). RunConfigResolve, + /// Persists editable run options (`runConfig.updateOptions`). RunConfigUpdateOptions, + /// Adds a user-authored run configuration (`runConfig.createUserConfiguration`). RunConfigCreateUserConfiguration, + /// Produces the process plan for one configuration (`runConfig.createLaunchPlan`). RunConfigCreateLaunchPlan, + /// Counts workspace uses of Java declarations (`java.codeVision`). JavaCodeVision, + /// Resolves a package-qualified Java class name (`java.className`). JavaClassName, + /// Finds a Java type or member declaration (`java.sourceDefinition`). JavaSourceDefinition, + /// Reads a Spring server port from configuration (`java.serverPort`). JavaServerPort, + /// Computes lightweight Java structure features (`java.structure`). JavaStructure, + /// Reads normalized repository and working-tree state (`git.status`). GitStatus, + /// Resolves paths a Git-aware watcher must observe (`git.watchContext`). GitWatchContext, + /// Executes a caller-supplied argument vector without a shell (`git.command`). GitCommand, + /// Performs one supported Git mutation (`git.write`). GitWrite, + /// Builds a structured Git diff (`git.diff`). GitDiff, + /// Applies a patch to the index or working tree (`git.apply`). GitApply, + /// Lists references and bounded commit history (`git.history`). GitHistory, + /// Resolves metadata for one commit (`git.commit`). GitCommit, + /// Lists paths changed by one commit (`git.commitFiles`). GitCommitFiles, + /// Compares a reference with the current checkout (`git.comparison`). GitComparison, + /// Lists repository stashes (`git.stashes`). GitStashes, + /// Finds edits that would block checkout (`git.checkoutPreflight`). GitCheckoutPreflight, + /// Reports whether the tracked branch can fast-forward (`git.pullPreflight`). GitPullPreflight, + /// Finds state that blocks merge, rebase, cherry-pick, or revert (`git.integrationPreflight`). GitIntegrationPreflight, + /// Finds staged files containing conflict markers (`git.conflictMarkers`). GitConflictMarkers, + /// Inspects an interrupted sequential Git operation (`git.operationState`). GitOperationState, + /// Returns normalized line attribution (`git.blame`). GitBlame, } impl CoreCommand { + /// Resolves a compatibility command name without accepting aliases or + /// case variations that could behave differently across hosts. pub fn parse(value: &str) -> Option { match value { "core.ping" => Some(Self::Ping), diff --git a/rust/lithe-core/src/protocol/contracts.rs b/rust/lithe-core/src/protocol/contracts.rs index 3310a88c..f544b8c9 100644 --- a/rust/lithe-core/src/protocol/contracts.rs +++ b/rust/lithe-core/src/protocol/contracts.rs @@ -1,29 +1,40 @@ +//! Serializable response models shared across every host boundary. + use crate::protocol::CoreError; use serde::Serialize; use serde_json::Value; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] +/// Stable success-or-failure envelope returned across the JSON and C boundaries. pub struct CoreResponse { + /// Correlation identifier copied from the request, when supplied. pub id: Option, + /// Discriminator that determines whether `data` or `error` is present. pub ok: bool, + /// Successful command payload. It is omitted for failures. #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, + /// Structured failure. It is omitted for successful responses. #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, } #[derive(Debug, Serialize)] #[serde(untagged)] +/// Payload wrapper that preserves each command's existing JSON shape. pub enum ResponseData { + /// A command-specific JSON value serialized without an additional tag. Json(Value), } impl CoreResponse { + /// Reports whether the response carries successful data. pub fn is_success(&self) -> bool { self.ok } + /// Builds a successful response while preserving the caller's identifier. pub fn success(id: Option, data: impl Into) -> Self { Self { id, @@ -33,6 +44,7 @@ impl CoreResponse { } } + /// Builds a failed response with no partially successful data attached. pub fn failure(id: Option, error: CoreError) -> Self { Self { id, @@ -45,6 +57,7 @@ impl CoreResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// One file or directory in a deterministic workspace tree. pub struct WorkspaceNode { pub path: String, pub name: String, @@ -55,6 +68,7 @@ pub struct WorkspaceNode { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Complete visible workspace tree and its flattened file paths. pub struct WorkspaceSnapshotResponse { pub root: WorkspaceNode, pub files: Vec, @@ -62,7 +76,9 @@ pub struct WorkspaceSnapshotResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// File, content, or symbol match with optional source location. pub struct SearchMatch { + /// Result category: file path, file content, or symbol. pub kind: String, pub path: String, pub line: Option, @@ -73,12 +89,14 @@ pub struct SearchMatch { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Bounded, deterministically ordered search matches. pub struct SearchResponse { pub matches: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Preview of replacements occurring on one source line. pub struct ReplacementMatch { pub line: usize, pub before: String, @@ -88,6 +106,7 @@ pub struct ReplacementMatch { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// All preview matches and resulting text for one file. pub struct ReplacementFile { pub path: String, pub matches: Vec, @@ -96,12 +115,14 @@ pub struct ReplacementFile { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Non-mutating replacement preview grouped by workspace-relative path. pub struct ReplacementPreviewResponse { pub files: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// UTF-8 contents read from one workspace-relative path. pub struct FileReadResponse { pub path: String, pub text: String, @@ -109,6 +130,7 @@ pub struct FileReadResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Path and byte count produced by a successful file write. pub struct FileWriteResponse { pub path: String, pub bytes_written: usize, @@ -116,6 +138,7 @@ pub struct FileWriteResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Metadata for one retained local-history snapshot. pub struct HistoryEntryResponse { pub id: String, pub timestamp: i64, @@ -129,12 +152,14 @@ pub struct HistoryEntryResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Local-history entries in deterministic newest-first order. pub struct HistoryEntriesResponse { pub entries: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Maven profile identity and its default activation state. pub struct MavenProfileResponse { pub id: String, pub is_active_by_default: bool, @@ -142,6 +167,7 @@ pub struct MavenProfileResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// One module in the declared Maven reactor hierarchy. pub struct MavenModuleResponse { pub relative_path: String, pub group_id: Option, @@ -153,6 +179,7 @@ pub struct MavenModuleResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Maven reactor identity, modules, profiles, and wrapper availability. pub struct MavenScanResponse { pub relative_path: String, pub group_id: Option, @@ -166,6 +193,7 @@ pub struct MavenScanResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// One normalized issue parsed from Maven process output. pub struct MavenDiagnosticResponse { pub path: String, pub line: usize, @@ -176,12 +204,14 @@ pub struct MavenDiagnosticResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Maven diagnostics in stable source order. pub struct MavenDiagnosticsResponse { pub issues: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Java class containing a runnable main method. pub struct JavaMainClassResponse { pub path: String, pub qualified_name: String, @@ -191,9 +221,11 @@ pub struct JavaMainClassResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// UI-facing Java or Spring Boot run entry. pub struct JavaRunConfigurationResponse { pub id: String, pub name: String, + /// UI category such as `javaMain`, `springBoot`, or `mavenModule`. pub kind: String, pub module_path: Option, pub main_class: Option, @@ -201,6 +233,7 @@ pub struct JavaRunConfigurationResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Discovered Java main classes and their stable run entries. pub struct JavaRunConfigurationsResponse { pub main_classes: Vec, pub configurations: Vec, @@ -208,6 +241,7 @@ pub struct JavaRunConfigurationsResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Usage count rendered above one Java declaration. pub struct JavaCodeVisionHintResponse { pub line: usize, pub utf16_column: usize, @@ -217,18 +251,21 @@ pub struct JavaCodeVisionHintResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Code Vision hints in source order. pub struct JavaCodeVisionResponse { pub hints: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Package-qualified Java class name. pub struct JavaClassNameResponse { pub class_name: String, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Zero-based UTF-16 location of a Java declaration. pub struct JavaSourceDefinitionResponse { pub line: usize, pub utf16_column: usize, @@ -236,13 +273,16 @@ pub struct JavaSourceDefinitionResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Server port declared by Spring configuration, when one is present. pub struct JavaServerPortResponse { pub port: Option, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Foldable Java source region and the text span hidden by folding. pub struct JavaFoldRegionResponse { + /// Fold category such as imports, declaration, or comment. pub kind: String, pub start_line: usize, pub end_line: usize, @@ -252,15 +292,18 @@ pub struct JavaFoldRegionResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Java gutter marker summarizing implementations of one declaration. pub struct JavaImplementationMarkerResponse { pub line: usize, pub utf16_column: usize, pub implementation_count: usize, + /// Navigation direction describing implementations below or parents above. pub direction: String, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Lightweight Java parameter-name inlay hint. pub struct JavaInlayHintResponse { pub line: usize, pub utf16_column: usize, @@ -269,6 +312,7 @@ pub struct JavaInlayHintResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Lightweight structural features derived from one Java source document. pub struct JavaStructureResponse { pub fold_regions: Vec, pub implementation_markers: Vec, @@ -277,10 +321,12 @@ pub struct JavaStructureResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// One normalized index or working-tree change. pub struct GitChange { pub path: String, #[serde(skip_serializing_if = "Option::is_none")] pub original_path: Option, + /// Normalized porcelain status code for the path. pub status: String, pub staged: bool, pub worktree: bool, @@ -289,6 +335,7 @@ pub struct GitChange { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Repository identity, branch divergence, and normalized file changes. pub struct GitStatusResponse { pub repository_root: Option, pub branch: Option, @@ -299,6 +346,7 @@ pub struct GitStatusResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Worktree-aware Git paths that a platform watcher should observe. pub struct GitWatchContextResponse { pub repository_root: String, pub git_directory: String, @@ -307,9 +355,11 @@ pub struct GitWatchContextResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Local or remote Git reference in display-ready form. pub struct GitReferenceResponse { pub full_name: String, pub short_name: String, + /// Reference category: local branch, remote branch, or tag. pub kind: String, pub is_current: bool, pub upstream_short_name: Option, @@ -317,6 +367,7 @@ pub struct GitReferenceResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Commit metadata parsed from a machine-stable field format. pub struct GitCommitResponse { pub hash: String, pub short_hash: String, @@ -330,6 +381,7 @@ pub struct GitCommitResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// References and a bounded page of commit history. pub struct GitHistoryResponse { pub references: Vec, pub commits: Vec, @@ -338,31 +390,37 @@ pub struct GitHistoryResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Exact lookup result for one commit. pub struct GitCommitLookupResponse { pub commit: GitCommitResponse, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Path and status changed by a commit or comparison. pub struct GitFileResponse { + /// Normalized name-status code such as `A`, `M`, `D`, or `R`. pub status: String, pub path: String, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Deterministically ordered changed paths. pub struct GitFilesResponse { pub files: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Paths differing between a reference and the current checkout. pub struct GitComparisonResponse { pub files: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// One stash entry with its stable reference and parsed metadata. pub struct GitStashResponse { pub reference: String, pub message: String, @@ -372,6 +430,7 @@ pub struct GitStashResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Stash entries in Git's native newest-first order. pub struct GitStashesResponse { pub stashes: Vec, } @@ -432,6 +491,7 @@ pub struct GitPullPreflightResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct GitOperationStateResponse { + /// Active operation name, or an empty string when no operation is in progress. pub kind: String, pub reference: Option, pub step: Option, @@ -441,6 +501,7 @@ pub struct GitOperationStateResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Line-level blame attribution normalized for editor gutters. pub struct GitBlameLineResponse { pub line: usize, pub commit_hash: String, @@ -450,12 +511,14 @@ pub struct GitBlameLineResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Blame attribution ordered by one-based source line. pub struct GitBlameResponse { pub lines: Vec, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// One aligned side-by-side diff row. pub struct GitDiffRowResponse { pub old_line: Option, pub new_line: Option, @@ -464,12 +527,14 @@ pub struct GitDiffRowResponse { /// hold identical text; clients fall back to `left` in that case. #[serde(skip_serializing_if = "Option::is_none")] pub right: Option, + /// Rendering category such as context, changed, insertion, deletion, or information. pub kind: String, pub hunk_id: Option, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Independently applicable diff hunk and its original patch text. pub struct GitDiffHunkResponse { pub id: String, pub header: String, @@ -478,6 +543,7 @@ pub struct GitDiffHunkResponse { #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Complete patch plus rendering rows and independently applicable hunks. pub struct GitDiffResponse { pub patch: String, pub rows: Vec, diff --git a/rust/lithe-core/src/protocol/error.rs b/rust/lithe-core/src/protocol/error.rs index 47c3fe0d..08b5c1a1 100644 --- a/rust/lithe-core/src/protocol/error.rs +++ b/rust/lithe-core/src/protocol/error.rs @@ -1,31 +1,53 @@ +//! Stable error categories and safe cross-boundary error serialization. + use serde::Serialize; #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "snake_case")] +/// Stable failure categories understood by all Core consumers. +/// +/// Keep these categories independent of Rust libraries and host operating +/// systems; implementation-specific context belongs in [`CoreError::details`]. pub enum ErrorCode { + /// The request envelope, payload, path, or operation name is invalid. InvalidRequest, + /// The requested workspace or repository root does not exist. WorkspaceNotFound, + /// The operation would escape an allowed root or lacks filesystem access. PermissionDenied, + /// The requested behavior is valid but unavailable in this Core build. NotSupported, + /// A required host-discovered executable or runtime is unavailable. RuntimeMissing, + /// A required child process could not be created. ProcessStartFailed, + /// A child process started but failed while serving the operation. ProcessFailed, + /// Input or tool output could not be decoded into the stable contract. ParseFailed, + /// The caller cooperatively cancelled the operation. Cancelled, + /// The operation exceeded its declared deadline. TimedOut, + /// A failure does not fit a more stable cross-platform category. Unknown, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] +/// Cross-boundary failure containing a stable category and actionable message. pub struct CoreError { + /// Machine-readable category used by application error handling. pub code: ErrorCode, + /// User-facing summary that is safe to display. pub message: String, + /// Optional diagnostic context that must not contain secrets. #[serde(skip_serializing_if = "Option::is_none")] pub details: Option, } impl CoreError { + /// Creates an error without implementation-specific details. pub fn new(code: ErrorCode, message: impl Into) -> Self { Self { code, @@ -34,6 +56,7 @@ impl CoreError { } } + /// Adds safe diagnostic context while preserving the stable error category. pub fn with_details(mut self, details: impl Into) -> Self { self.details = Some(details.into()); self diff --git a/rust/lithe-core/src/protocol/event.rs b/rust/lithe-core/src/protocol/event.rs index 3a8ca6e6..3e0d8556 100644 --- a/rust/lithe-core/src/protocol/event.rs +++ b/rust/lithe-core/src/protocol/event.rs @@ -1,13 +1,24 @@ +//! Events emitted by asynchronous shared-core operations. + use crate::protocol::CoreError; use crate::protocol::{GitStatusResponse, SearchResponse, WorkspaceSnapshotResponse}; use serde::Serialize; #[derive(Debug, Clone, Serialize)] #[serde(tag = "type", content = "payload", rename_all = "camelCase")] +/// Tagged asynchronous events delivered to host applications. pub enum CoreEvent { + /// A workspace snapshot finished loading. WorkspaceLoaded(WorkspaceSnapshotResponse), + /// An asynchronous search produced its final result set. SearchCompleted(SearchResponse), + /// Observed Git state changed for the active repository. GitStatusChanged(GitStatusResponse), - FileChanged { path: String }, + /// A workspace-relative path changed on disk. + FileChanged { + /// Forward-slashed path relative to the workspace root. + path: String, + }, + /// An asynchronous Core operation failed before producing data. OperationFailed(CoreError), } diff --git a/rust/lithe-core/src/runtime/dispatcher.rs b/rust/lithe-core/src/runtime/dispatcher.rs index 2a0cc27e..799fb65f 100644 --- a/rust/lithe-core/src/runtime/dispatcher.rs +++ b/rust/lithe-core/src/runtime/dispatcher.rs @@ -1,3 +1,5 @@ +//! Validation and routing from versioned command names to their owning domains. + use crate::git::{ self, GitApplyRequest, GitBlameRequest, GitCheckoutPreflightRequest, GitCommandRequest, GitCommitFilesRequest, GitCommitRequest, GitComparisonRequest, GitConflictMarkerRequest, diff --git a/rust/lithe-core/src/runtime/ffi.rs b/rust/lithe-core/src/runtime/ffi.rs index 7354b5ea..b26c5687 100644 --- a/rust/lithe-core/src/runtime/ffi.rs +++ b/rust/lithe-core/src/runtime/ffi.rs @@ -1,13 +1,28 @@ +//! Ownership-safe C ABI wrappers for the JSON command and cancellation APIs. + use std::ffi::{CStr, CString}; use std::os::raw::c_char; use std::path::PathBuf; +/// Returns a pointer to the static, NUL-terminated Core ABI version. +/// +/// The pointer remains valid for the lifetime of the process and must not be +/// passed to [`lithe_core_free_string`]. #[no_mangle] pub extern "C" fn lithe_core_version() -> *const c_char { static VERSION: &[u8] = b"0.1.0\0"; VERSION.as_ptr().cast() } +/// Executes one JSON request through the stable C ABI. +/// +/// The returned string is owned by the caller and must be released exactly +/// once with [`lithe_core_free_string`]. +/// +/// # Safety +/// +/// `request` must be null or point to a readable, NUL-terminated byte string +/// for the duration of this call. #[no_mangle] pub unsafe extern "C" fn lithe_core_execute_json(request: *const c_char) -> *mut c_char { if request.is_null() { @@ -19,6 +34,15 @@ pub unsafe extern "C" fn lithe_core_execute_json(request: *const c_char) -> *mut response_pointer(&crate::execute_json(&request)) } +/// Loads the merged language-provider catalog for an optional workspace root. +/// +/// The returned string is owned by the caller and must be released exactly +/// once with [`lithe_core_free_string`]. +/// +/// # Safety +/// +/// `workspace_root` must be null or point to a readable, NUL-terminated byte +/// string for the duration of this call. #[no_mangle] pub unsafe extern "C" fn lithe_core_lsp_provider_catalog_json( workspace_root: *const c_char, @@ -38,6 +62,11 @@ pub unsafe extern "C" fn lithe_core_lsp_provider_catalog_json( /// Requests cooperative cancellation of an in-flight operation. The call is /// thread-safe and returns 1 when an active operation was found. +/// +/// # Safety +/// +/// `operation_id` must be null or point to a readable, NUL-terminated byte +/// string for the duration of this call. #[no_mangle] pub unsafe extern "C" fn lithe_core_cancel(operation_id: *const c_char) -> i32 { if operation_id.is_null() { @@ -47,6 +76,13 @@ pub unsafe extern "C" fn lithe_core_cancel(operation_id: *const c_char) -> i32 { crate::cancel_operation(&operation_id) as i32 } +/// Releases a string returned by a Core C ABI function. +/// +/// # Safety +/// +/// `value` must be null or a pointer returned by this library that has not +/// already been freed. Static pointers such as [`lithe_core_version`] are not +/// owned strings and must not be passed here. #[no_mangle] pub unsafe extern "C" fn lithe_core_free_string(value: *mut c_char) { if !value.is_null() { diff --git a/rust/lithe-core/src/tests/project.rs b/rust/lithe-core/src/tests/project.rs index e4d17d63..63553ae5 100644 --- a/rust/lithe-core/src/tests/project.rs +++ b/rust/lithe-core/src/tests/project.rs @@ -271,7 +271,7 @@ fn file_mask_limits_search_to_matching_extensions() { assert!(java_only.iter().any(|path| path.ends_with("Service.java"))); assert!(!java_only.iter().any(|path| path.ends_with("notes.txt"))); - // 多个掩码取并集,且容忍逗号后的空格。 + // Multiple masks form a union, and whitespace after commas is ignored. let both = search("*.java, *.txt"); assert!(both.iter().any(|path| path.ends_with("Service.java"))); assert!(both.iter().any(|path| path.ends_with("notes.txt")));