Skip to content

Commit 0a6c95e

Browse files
feat(Common): Add core effect modules for extension integration
- Introduce effect modules for commands, configuration, diagnostics, documents, IPC, output, secrets, storage, UI, and workspace operations in Common element - Implement trait-based effect system using async-trait to bridge Mountain backend with extension capabilities - Define DTO structures aligned with VS Code extension protocol for Cocoon sidecar compatibility - Create ActionEffect infrastructure to encapsulate environment capabilities through AppRuntime - Establish foundation for MVP Path A by enabling extension command execution and configuration management via Cocoon shims - Prepare workspace/file system interaction patterns required for language feature providers - Implement error handling system with CommonError enum covering extension operation scenarios This architectural work enables Mountain to handle VS Code extension API surface through Cocoon sidecar while maintaining Rust-native performance. Effects will be dispatched through Track command router and communicated via Vine IPC layer.
1 parent 4fda6f9 commit 0a6c95e

16 files changed

Lines changed: 2184 additions & 80 deletions

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ serde = { version = "1.0.219", features = ["derive"] }
33
toml = { version = "0.8.20" }
44

55
[dependencies]
6+
async-trait = { workspace = true }
7+
serde = { workspace = true }
8+
serde_json = { workspace = true }
9+
url = { workspace = true }
10+
611

712
[features]
8-
Development = ["tokio-console"]
913
default = []
1014

1115
[lib]

Source/command_effects.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Land_Common/src/command_effects.rs
2+
use std::sync::Arc;
3+
4+
use async_trait::async_trait;
5+
use serde_json::Value;
6+
7+
// Ensure AppRuntime is the correct type from your runtime module.
8+
// This accessor is used by effects to get to the concrete environment.
9+
use crate::runtime::AppRuntime;
10+
use crate::{
11+
effect::ActionEffect,
12+
environment::{Environment, Requires},
13+
errors::CommonError,
14+
};
15+
16+
/// Trait for executing and managing commands within the application.
17+
///
18+
/// An environment implementing this trait can execute predefined or dynamically
19+
/// registered commands, often involving IPC with sidecars or native handlers.
20+
#[async_trait]
21+
pub trait CommandExecutor: Environment {
22+
/// Executes a command with the given ID and arguments.
23+
async fn execute_command(&self, command_id:String, args:Value) -> Result<Value, CommonError>;
24+
25+
/// Registers a command that can be executed, typically associating it with
26+
/// a sidecar.
27+
async fn register_command(&self, sidecar_id:String, command_id:String) -> Result<(), CommonError>;
28+
29+
/// Unregisters a previously registered command.
30+
async fn unregister_command(&self, sidecar_id:String, command_id:String) -> Result<(), CommonError>;
31+
32+
/// Retrieves a list of all currently registered command IDs.
33+
async fn get_all_commands(&self) -> Result<Vec<String>, CommonError>;
34+
}
35+
36+
// --- Effect Constructors ---
37+
38+
/// Creates an effect to execute a command.
39+
///
40+
/// The effect, when run, will use the `CommandExecutor` capability of the
41+
/// environment to dispatch the command.
42+
pub fn execute_command(command_id:String, args:Value) -> ActionEffect<Arc<AppRuntime>, CommonError, Value> {
43+
// Effect expects Arc<AppRuntime>
44+
ActionEffect::new(Arc::new(move |app_runtime_accessor:Arc<AppRuntime>| {
45+
let cid_clone = command_id.clone();
46+
let args_clone = args.clone();
47+
Box::pin(async move {
48+
// Get the concrete environment (e.g., MountainEnvironment)
49+
let concrete_env = app_runtime_accessor.get_environment();
50+
// Require the CommandExecutor capability
51+
let executor:Arc<dyn CommandExecutor + Send + Sync> = concrete_env.require();
52+
executor.execute_command(cid_clone, args_clone).await
53+
})
54+
}))
55+
}
56+
57+
/// Creates an effect to register a command.
58+
pub fn register_command(sidecar_id:String, command_id:String) -> ActionEffect<Arc<AppRuntime>, CommonError, ()> {
59+
// Effect expects Arc<AppRuntime>
60+
ActionEffect::new(Arc::new(move |app_runtime_accessor:Arc<AppRuntime>| {
61+
let sid_clone = sidecar_id.clone();
62+
let cid_clone = command_id.clone();
63+
Box::pin(async move {
64+
let concrete_env = app_runtime_accessor.get_environment();
65+
let executor:Arc<dyn CommandExecutor + Send + Sync> = concrete_env.require();
66+
executor.register_command(sid_clone, cid_clone).await
67+
})
68+
}))
69+
}
70+
71+
/// Creates an effect to unregister a command.
72+
pub fn unregister_command(sidecar_id:String, command_id:String) -> ActionEffect<Arc<AppRuntime>, CommonError, ()> {
73+
// Effect expects Arc<AppRuntime>
74+
ActionEffect::new(Arc::new(move |app_runtime_accessor:Arc<AppRuntime>| {
75+
let sid_clone = sidecar_id.clone();
76+
let cid_clone = command_id.clone();
77+
Box::pin(async move {
78+
let concrete_env = app_runtime_accessor.get_environment();
79+
let executor:Arc<dyn CommandExecutor + Send + Sync> = concrete_env.require();
80+
executor.unregister_command(sid_clone, cid_clone).await
81+
})
82+
}))
83+
}
84+
85+
/// Creates an effect to get all registered command IDs.
86+
pub fn get_all_commands() -> ActionEffect<Arc<AppRuntime>, CommonError, Vec<String>> {
87+
// Effect expects Arc<AppRuntime>
88+
ActionEffect::new(Arc::new(move |app_runtime_accessor:Arc<AppRuntime>| {
89+
Box::pin(async move {
90+
let concrete_env = app_runtime_accessor.get_environment();
91+
let executor:Arc<dyn CommandExecutor + Send + Sync> = concrete_env.require();
92+
executor.get_all_commands().await
93+
})
94+
}))
95+
}

Source/config_effects.rs

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// Land_Common/src/config_effects.rs
2+
use std::sync::Arc;
3+
4+
use async_trait::async_trait;
5+
use serde::{Deserialize, Serialize};
6+
use serde_json::Value;
7+
8+
use crate::environment::{Environment, Requires}; // For trait bounds and effect context
9+
use crate::runtime::AppRuntime; // Assumed concrete runtime accessor for effects
10+
use crate::{effect::ActionEffect, errors::CommonError};
11+
12+
// --- Configuration Enums and DTOs ---
13+
14+
/// Defines the target level for a configuration update.
15+
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
16+
#[serde(rename_all = "camelCase")] // For consistency if serialized, though u32 doesn't need it
17+
pub enum ConfigurationTarget {
18+
UserLocal = 1, // User settings specific to the current machine/installation.
19+
User = 2, // User settings, potentially synced across machines.
20+
Workspace = 3, // Settings applicable to the entire workspace.
21+
WorkspaceFolder = 4, // Settings specific to a folder within a multi-root workspace.
22+
Default = 5, // Default values provided by the application or extensions.
23+
Memory = 6, // In-memory overrides, not persisted.
24+
Policy = 7, // Configuration enforced by policies (e.g., enterprise).
25+
}
26+
27+
/// Defines the scope of a configuration value, indicating where it can be
28+
/// applied. Aligns with VS Code's `vscode.ConfigurationScope`.
29+
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
30+
#[serde(rename_all = "camelCase")]
31+
pub enum ConfigurationScope {
32+
Application = 1, // Applies to the entire application, not specific to any window/workspace.
33+
Machine = 2, /* Applies to the machine, not specific to user or workspace. (Less common for user
34+
* settings) */
35+
Window = 3, // Applies to a specific window/workspace instance.
36+
Resource = 4, // Applies to a specific resource (e.g., file URI).
37+
LanguageDefined = 5, // Language-specific override for a setting at any of the above scopes.
38+
MachineOverridable = 6, // Like Machine, but can be overridden by workspace/user settings.
39+
}
40+
41+
/// DTO for specifying overrides when retrieving or inspecting configuration
42+
/// values. This often includes a resource URI (for resource or folder-specific
43+
/// settings) and a language identifier (for language-specific settings).
44+
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
45+
#[serde(rename_all = "camelCase")]
46+
pub struct IConfigurationOverrides {
47+
/// The resource URI to which the configuration should be scoped.
48+
/// Typically a `Value` representing `UriComponents` DTO.
49+
#[serde(skip_serializing_if = "Option::is_none")]
50+
pub resource:Option<Value>,
51+
52+
/// The language identifier for language-specific overrides.
53+
#[serde(skip_serializing_if = "Option::is_none")]
54+
pub override_identifier:Option<String>,
55+
// VS Code also has `overrideIdentifiers` (plural, string[]) for more complex language overrides.
56+
// This DTO uses a single `override_identifier` for simplicity.
57+
}
58+
59+
/// DTO representing the initial configuration data structure, often sent
60+
/// from the main thread (Mountain) to a sidecar (Cocoon) on startup or after
61+
/// significant configuration changes.
62+
/// It includes values from different configuration sources (default, user,
63+
/// workspace, etc.).
64+
#[derive(Serialize, Deserialize, Debug, Clone)]
65+
#[serde(rename_all = "camelCase")]
66+
pub struct IConfigurationInitDataDto {
67+
pub effective:Value, // The final, merged configuration values.
68+
pub defaults:Value, // Default values (e.g., `{ "contents": { "editor.fontSize": 12 } }`).
69+
pub user:Value, // User-level settings.
70+
pub workspace:Value, // Workspace-level settings.
71+
// Array of `[UriComponentsDto, { "contents": object }]` for multi-root workspace folder settings.
72+
pub folders:Value,
73+
pub memory:Value, // In-memory settings.
74+
#[serde(skip_serializing_if = "Option::is_none")]
75+
pub policy:Option<Value>, // Policy-enforced settings.
76+
// Optional: Detailed scope information for each configuration key.
77+
// Array of `[key_string, ConfigurationScope_enum_as_Value]`
78+
#[serde(skip_serializing_if = "Option::is_none")]
79+
pub configuration_scopes:Option<Vec<(String, Value)>>,
80+
}
81+
82+
/// DTO for the result of inspecting a configuration key.
83+
/// Provides values from different scopes (user, workspace, default, etc.)
84+
/// and the final effective value.
85+
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
86+
#[serde(rename_all = "camelCase")]
87+
pub struct InspectResultData {
88+
// Using Value for flexibility, as the actual type `T` of a config key can vary.
89+
pub default_value:Option<Value>,
90+
pub user_value:Option<Value>, // Combined local & remote user settings.
91+
pub user_local_value:Option<Value>, // Specifically local user settings.
92+
pub user_remote_value:Option<Value>, // Specifically remote/synced user settings.
93+
pub workspace_value:Option<Value>,
94+
pub workspace_folder_value:Option<Value>,
95+
pub memory_value:Option<Value>, // In-memory overrides.
96+
pub policy_value:Option<Value>, // Policy-enforced values.
97+
pub effective_value:Option<Value>, // The final value after all merging and overrides.
98+
99+
// Values specific to a language override, if applicable.
100+
pub default_language_value:Option<Value>,
101+
pub user_language_value:Option<Value>,
102+
pub user_local_language_value:Option<Value>,
103+
pub user_remote_language_value:Option<Value>,
104+
pub workspace_language_value:Option<Value>,
105+
pub workspace_folder_language_value:Option<Value>,
106+
pub memory_language_value:Option<Value>,
107+
pub policy_language_value:Option<Value>,
108+
109+
/// List of language identifiers for which language-specific overrides exist
110+
/// for this key.
111+
#[serde(skip_serializing_if = "Option::is_none")]
112+
pub language_ids:Option<Vec<String>>,
113+
// More detailed scope information (similar to VS Code's IConfigurationInspectInfo<T>)
114+
// could be added here if needed, e.g., indicating which scope provided the override.
115+
// pub default: Option<{value?: Value, override?: Value, scope?: ConfigurationScope}>,
116+
// pub user: Option<{value?: Value, override?: Value, scope?: ConfigurationScope}>,
117+
// ... etc.
118+
}
119+
120+
// --- Configuration Provider Traits ---
121+
122+
/// Trait for an environment component that can provide and update configuration
123+
/// values.
124+
#[async_trait]
125+
pub trait ConfigProvider: Environment {
126+
/// Retrieves a configuration value for a given section/key, applying
127+
/// specified overrides.
128+
async fn get_configuration_value(
129+
&self,
130+
section:Option<String>, // Dot-separated key, or None for all.
131+
overrides:IConfigurationOverrides,
132+
) -> Result<Value, CommonError>;
133+
134+
/// Updates a configuration value at a specific key and target scope.
135+
async fn update_configuration_value(
136+
&self,
137+
key:String, // Dot-separated key to update.
138+
value:Value, // New value (Value::Null to remove).
139+
target:ConfigurationTarget, // Where to write the update (User, Workspace, etc.).
140+
overrides:IConfigurationOverrides, // Context for WorkspaceFolder target or language overrides.
141+
scope_to_language:Option<bool>, // If true, update within language-specific section `[languageId]`.
142+
) -> Result<(), CommonError>;
143+
}
144+
145+
/// Trait for an environment component that can inspect configuration values,
146+
/// providing details about their sources and effective values.
147+
#[async_trait]
148+
pub trait ConfigInspector: Environment {
149+
/// Inspects a configuration key to get its value from all relevant scopes.
150+
async fn inspect_configuration_value(
151+
&self,
152+
key:String, // Dot-separated key to inspect.
153+
overrides:IConfigurationOverrides, // Context for resource/language.
154+
) -> Result<Option<InspectResultData>, CommonError>;
155+
}
156+
157+
// --- Effect Constructors ---
158+
159+
/// Creates an effect to get a configuration value.
160+
pub fn get_configuration(
161+
section:Option<String>,
162+
overrides_dto_val:Value, // IConfigurationOverrides serialized as JSON Value
163+
_scope_to_language:Option<bool>, /* This param seems misplaced for get_configuration, more for update. Included
164+
* if protocol sends it. */
165+
) -> ActionEffect<Arc<AppRuntime>, CommonError, Value> {
166+
// Expects Arc<AppRuntime>
167+
ActionEffect::new(Arc::new(move |app_runtime_accessor:Arc<AppRuntime>| {
168+
let section_clone = section.clone();
169+
let overrides_val_clone = overrides_dto_val.clone();
170+
Box::pin(async move {
171+
let concrete_env = app_runtime_accessor.get_environment();
172+
let provider:Arc<dyn ConfigProvider + Send + Sync> = concrete_env.require();
173+
174+
let overrides_parsed:IConfigurationOverrides = serde_json::from_value(overrides_val_clone.clone())
175+
.map_err(|e| {
176+
CommonError::InvalidArg(
177+
"overrides_dto".to_string(),
178+
format!("Failed to parse IConfigurationOverrides from {:?}: {}", overrides_val_clone, e),
179+
)
180+
})?;
181+
provider.get_configuration_value(section_clone, overrides_parsed).await
182+
})
183+
}))
184+
}
185+
186+
/// Creates an effect to update a configuration value.
187+
pub fn update_configuration(
188+
key:String,
189+
value:Value,
190+
target_as_u32:u32, // ConfigurationTarget enum value as u32
191+
overrides_dto_val:Value, // IConfigurationOverrides serialized as JSON Value
192+
scope_to_language:Option<bool>,
193+
) -> ActionEffect<Arc<AppRuntime>, CommonError, ()> {
194+
// Expects Arc<AppRuntime>
195+
ActionEffect::new(Arc::new(move |app_runtime_accessor:Arc<AppRuntime>| {
196+
let key_clone = key.clone();
197+
let value_clone = value.clone();
198+
let overrides_val_clone = overrides_dto_val.clone();
199+
let stl_clone = scope_to_language;
200+
201+
Box::pin(async move {
202+
let concrete_env = app_runtime_accessor.get_environment();
203+
let provider:Arc<dyn ConfigProvider + Send + Sync> = concrete_env.require();
204+
205+
// Deserialize ConfigurationTarget from u32 via Value
206+
let target_parsed:ConfigurationTarget =
207+
serde_json::from_value(Value::from(target_as_u32)).map_err(|e| {
208+
CommonError::InvalidArg(
209+
"target".to_string(),
210+
format!("Failed to parse ConfigurationTarget from u32 {}: {}", target_as_u32, e),
211+
)
212+
})?;
213+
214+
let overrides_parsed:IConfigurationOverrides = serde_json::from_value(overrides_val_clone.clone())
215+
.map_err(|e| {
216+
CommonError::InvalidArg(
217+
"overrides_dto".to_string(),
218+
format!("Failed to parse IConfigurationOverrides from {:?}: {}", overrides_val_clone, e),
219+
)
220+
})?;
221+
222+
provider
223+
.update_configuration_value(key_clone, value_clone, target_parsed, overrides_parsed, stl_clone)
224+
.await
225+
})
226+
}))
227+
}
228+
229+
/// Creates an effect to inspect a configuration value.
230+
pub fn inspect_configuration_value(
231+
key:String,
232+
overrides_dto_val:Value, // IConfigurationOverrides serialized as JSON Value
233+
) -> ActionEffect<Arc<AppRuntime>, CommonError, Option<InspectResultData>> {
234+
// Expects Arc<AppRuntime>
235+
ActionEffect::new(Arc::new(move |app_runtime_accessor:Arc<AppRuntime>| {
236+
let key_clone = key.clone();
237+
let overrides_val_clone = overrides_dto_val.clone();
238+
Box::pin(async move {
239+
let concrete_env = app_runtime_accessor.get_environment();
240+
let inspector:Arc<dyn ConfigInspector + Send + Sync> = concrete_env.require();
241+
242+
let overrides_parsed:IConfigurationOverrides = serde_json::from_value(overrides_val_clone.clone())
243+
.map_err(|e| {
244+
CommonError::InvalidArg(
245+
"overrides_dto".to_string(),
246+
format!("Failed to parse IConfigurationOverrides from {:?}: {}", overrides_val_clone, e),
247+
)
248+
})?;
249+
inspector.inspect_configuration_value(key_clone, overrides_parsed).await
250+
})
251+
}))
252+
}

0 commit comments

Comments
 (0)