Skip to content

Commit bb240bc

Browse files
Re-sync
1 parent 5bcdf79 commit bb240bc

58 files changed

Lines changed: 4630 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,16 @@ uuid = { workspace = true, features = ["v4", "serde"] }
3636
chrono = { workspace = true, features = ["serde"] }
3737
thiserror = { workspace = true }
3838
Echo = { workspace = true }
39+
Common = { workspace = true }
3940
serde_json = { workspace = true }
4041
serde = { workspace = true }
4142
toml = { workspace = true }
43+
md5 = { workspace = true }
44+
num_cpus = { workspace = true }
45+
rand = { workspace = true }
4246

4347
tauri-plugin-dialog = { workspace = true }
48+
4449
# regex = { workspace = true }
4550
# unbug = { workspace = true }
4651

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
//! # ApplicationState Struct
2+
//!
3+
//! Defines the main `ApplicationState` struct, which is the central, shared,
4+
//! thread-safe state container for the entire Mountain application. It is
5+
//! managed by Tauri and is accessible to all command handlers and Environment
6+
//! providers.
7+
8+
use std::{
9+
collections::HashMap,
10+
path::{Path, PathBuf},
11+
sync::{
12+
Arc,
13+
Mutex as StandardMutex,
14+
atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering as AtomicOrdering},
15+
},
16+
};
17+
18+
use log::{error, info, warn};
19+
use tauri::{AppHandle, Manager, Runtime};
20+
21+
use super::{DTO::*, Internal};
22+
use crate::Environment::CommandProvider::CommandHandler; // TODO: Fix this path after refactor
23+
24+
/// The central, shared, thread-safe state for the entire Mountain application.
25+
///
26+
/// This struct consolidates all dynamic state required by the backend, from
27+
/// workspace information and configuration to the state of active UI components
28+
/// like terminals and WebViews.
29+
#[derive(Clone)]
30+
pub struct ApplicationState {
31+
// --- Workspace State ---
32+
pub WorkspaceFolders:Arc<StandardMutex<Vec<WorkspaceFolderStateDTO>>>,
33+
pub WorkspaceConfigurationPath:Arc<StandardMutex<Option<PathBuf>>>,
34+
pub IsTrusted:Arc<AtomicBool>,
35+
pub WindowState:Arc<StandardMutex<WindowStateDTO>>,
36+
37+
// --- Configuration & Storage ---
38+
pub Configuration:Arc<StandardMutex<MergedConfigurationStateDTO>>,
39+
pub GlobalMemento:Arc<StandardMutex<HashMap<String, serde_json::Value>>>,
40+
pub GlobalMementoPath:PathBuf,
41+
pub WorkspaceMemento:Arc<StandardMutex<HashMap<String, serde_json::Value>>>,
42+
pub WorkspaceMementoPath:Arc<StandardMutex<Option<PathBuf>>>,
43+
44+
// --- Extension & Provider Management ---
45+
pub CommandRegistry:Arc<StandardMutex<HashMap<String, CommandHandler<Runtime>>>>,
46+
pub LanguageProviders:Arc<StandardMutex<HashMap<u32, ProviderRegistrationDTO>>>,
47+
pub NextProviderHandle:Arc<AtomicU32>,
48+
pub ScannedExtensions:Arc<StandardMutex<HashMap<String, ExtensionDescriptionStateDTO>>>,
49+
pub EnabledProposedAPIs:Arc<StandardMutex<HashMap<String, Vec<String>>>>,
50+
pub ExtensionScanPaths:Arc<StandardMutex<Vec<PathBuf>>>,
51+
52+
// --- Feature-specific State ---
53+
pub DiagnosticsMap:Arc<StandardMutex<HashMap<String, HashMap<String, Vec<MarkerDataDTO>>>>>,
54+
pub OpenDocuments:Arc<StandardMutex<HashMap<String, DocumentStateDTO>>>,
55+
pub OutputChannels:Arc<StandardMutex<HashMap<String, OutputChannelStateDTO>>>,
56+
pub ActiveTerminals:Arc<StandardMutex<HashMap<u64, Arc<StandardMutex<TerminalStateDTO>>>>>,
57+
pub NextTerminalIdentifier:Arc<AtomicU64>,
58+
pub ActiveWebViews:Arc<StandardMutex<HashMap<String, WebViewStateDTO>>>,
59+
pub ActiveCustomDocuments:Arc<StandardMutex<HashMap<String, CustomDocumentStateDTO>>>,
60+
pub ActiveStatusBarItems:Arc<StandardMutex<HashMap<String, Common::StatusBar::DTO::StatusBarEntryDTO>>>,
61+
pub ActiveTreeViews:Arc<StandardMutex<HashMap<String, TreeViewStateDTO>>>,
62+
63+
// --- IPC & User Interface State ---
64+
pub PendingUserInterfaceRequests: Arc<
65+
StandardMutex<
66+
HashMap<String, tokio::sync::oneshot::Sender<Result<serde_json::Value, Common::Error::CommonError>>>,
67+
>,
68+
>,
69+
}
70+
71+
impl Default for ApplicationState {
72+
fn default() -> Self {
73+
info!("[ApplicationState] Initializing default application state...");
74+
let ApplicationNameForPaths = env!("CARGO_PKG_NAME");
75+
let ApplicationDataDirectoryPath =
76+
dirs::config_dir().map(|p| p.join(ApplicationNameForPaths)).unwrap_or_else(|| {
77+
warn!(
78+
"[ApplicationState] Could not get config dir. Using relative path '.{}-appdata'.",
79+
ApplicationNameForPaths
80+
);
81+
PathBuf::from(format!(".{}-appdata", ApplicationNameForPaths))
82+
});
83+
84+
// This must be synchronous because the async runtime isn't available yet.
85+
if !ApplicationDataDirectoryPath.exists() {
86+
if let Err(e) = std::fs::create_dir_all(&ApplicationDataDirectoryPath) {
87+
error!(
88+
"[ApplicationState] CRITICAL: Failed to create app data directory at '{}': {}.",
89+
ApplicationDataDirectoryPath.display(),
90+
e
91+
);
92+
}
93+
}
94+
95+
let GlobalMementoFilePath = Internal::ResolveMementoStorageFilePath(&ApplicationDataDirectoryPath, true, "");
96+
let InitialGlobalMementoMap = Internal::LoadInitialMementoFromDisk(&GlobalMementoFilePath);
97+
// let InitialCommandRegistryMap =
98+
// crate::Handler::Command::RegisterNativeCommands(); // TODO: Re-integrate
99+
100+
info!("[ApplicationState] Default state initialization complete.");
101+
Self {
102+
WorkspaceFolders:Arc::new(StandardMutex::new(Vec::new())),
103+
WorkspaceConfigurationPath:Arc::new(StandardMutex::new(None)),
104+
IsTrusted:Arc::new(AtomicBool::new(false)),
105+
WindowState:Arc::new(StandardMutex::new(Default::default())),
106+
Configuration:Arc::new(StandardMutex::new(MergedConfigurationStateDTO::default())),
107+
GlobalMemento:Arc::new(StandardMutex::new(InitialGlobalMementoMap)),
108+
GlobalMementoPath:GlobalMementoFilePath,
109+
WorkspaceMemento:Arc::new(StandardMutex::new(HashMap::new())),
110+
WorkspaceMementoPath:Arc::new(StandardMutex::new(None)),
111+
CommandRegistry:Arc::new(StandardMutex::new(HashMap::new())), // TODO: Use InitialCommandRegistryMap
112+
DiagnosticsMap:Arc::new(StandardMutex::new(HashMap::new())),
113+
OpenDocuments:Arc::new(StandardMutex::new(HashMap::new())),
114+
OutputChannels:Arc::new(StandardMutex::new(HashMap::new())),
115+
LanguageProviders:Arc::new(StandardMutex::new(HashMap::new())),
116+
NextProviderHandle:Arc::new(AtomicU32::new(1)),
117+
ScannedExtensions:Arc::new(StandardMutex::new(HashMap::new())),
118+
EnabledProposedAPIs:Arc::new(StandardMutex::new(HashMap::new())),
119+
ExtensionScanPaths:Arc::new(StandardMutex::new(Vec::new())),
120+
ActiveTerminals:Arc::new(StandardMutex::new(HashMap::new())),
121+
NextTerminalIdentifier:Arc::new(AtomicU64::new(1)),
122+
PendingUserInterfaceRequests:Arc::new(StandardMutex::new(HashMap::new())),
123+
ActiveWebViews:Arc::new(StandardMutex::new(HashMap::new())),
124+
ActiveCustomDocuments:Arc::new(StandardMutex::new(HashMap::new())),
125+
ActiveStatusBarItems:Arc::new(StandardMutex::new(HashMap::new())),
126+
ActiveTreeViews:Arc::new(StandardMutex::new(HashMap::new())),
127+
}
128+
}
129+
}
130+
131+
impl ApplicationState {
132+
/// Generates a unique, filesystem-safe identifier string for the current
133+
/// workspace.
134+
pub fn GetWorkspaceIdentifier(&self) -> Result<String, String> {
135+
let LockErrorMapper = |e| format!("[AppState] Lock error: {}", e);
136+
let ConfigurationPathGuard = self.WorkspaceConfigurationPath.lock().map_err(LockErrorMapper)?;
137+
if let Some(ConfigurationPath) = ConfigurationPathGuard.as_ref() {
138+
return Ok(ConfigurationPath.file_name().unwrap_or_default().to_string_lossy().into_owned());
139+
}
140+
drop(ConfigurationPathGuard);
141+
142+
let FoldersGuard = self.WorkspaceFolders.lock().map_err(LockErrorMapper)?;
143+
if let Some(FirstFolder) = FoldersGuard.first() {
144+
let PathString = FirstFolder.URI.path();
145+
// Create a more stable hash for the identifier.
146+
let Hash = format!("{:x}", md5::compute(PathString));
147+
return Ok(format!(
148+
"{}-{}",
149+
FirstFolder.Name.replace(|c:char| !c.is_alphanumeric(), "_"),
150+
&Hash[..8]
151+
));
152+
}
153+
154+
Ok("NO_WORKSPACE".to_string())
155+
}
156+
157+
/// Returns the next available unique identifier for a language provider
158+
/// registration.
159+
pub fn GetNextProviderHandle(&self) -> u32 { self.NextProviderHandle.fetch_add(1, AtomicOrdering::Relaxed) }
160+
161+
/// Returns the next available unique identifier for a terminal instance.
162+
pub fn GetNextTerminalIdentifier(&self) -> u64 { self.NextTerminalIdentifier.fetch_add(1, AtomicOrdering::Relaxed) }
163+
164+
/// Updates the path to the workspace memento file and reloads its content
165+
/// from disk.
166+
pub fn UpdateWorkspaceMementoPathAndReload(&self, ApplicationDataDirectory:&Path) -> Result<(), String> {
167+
let LockErrorMapper = |e| format!("[AppState] Lock error: {}", e);
168+
let WorkspaceIdentifier = self.GetWorkspaceIdentifier()?;
169+
let mut PathGuard = self.WorkspaceMementoPath.lock().map_err(LockErrorMapper)?;
170+
171+
if WorkspaceIdentifier == "NO_WORKSPACE" {
172+
if PathGuard.is_some() {
173+
*PathGuard = None;
174+
self.WorkspaceMemento.lock().map_err(LockErrorMapper)?.clear();
175+
}
176+
return Ok(());
177+
}
178+
179+
let NewMementoPath =
180+
Internal::ResolveMementoStorageFilePath(ApplicationDataDirectory, false, &WorkspaceIdentifier);
181+
if PathGuard.as_ref() != Some(&NewMementoPath) {
182+
if let Some(Parent) = NewMementoPath.parent() {
183+
if !Parent.exists() {
184+
std::fs::create_dir_all(Parent).map_err(|e| e.to_string())?;
185+
}
186+
}
187+
*PathGuard = Some(NewMementoPath.clone());
188+
let NewMementoContent = Internal::LoadInitialMementoFromDisk(&NewMementoPath);
189+
*self.WorkspaceMemento.lock().map_err(LockErrorMapper)? = NewMementoContent;
190+
}
191+
Ok(())
192+
}
193+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//! # CustomDocumentStateDTO
2+
//!
3+
//! Defines the Data Transfer Object for storing the state of a single custom
4+
//! editor document.
5+
6+
#![allow(non_snake_case, non_camel_case_types)]
7+
8+
use std::collections::HashMap;
9+
10+
use serde::{Deserialize, Serialize};
11+
use url::Url;
12+
13+
use crate::ApplicationState::Internal::URLSerializationHelper;
14+
15+
/// A struct that holds the state for a document being handled by a custom
16+
/// editor. This is stored in `ApplicationState` to track the lifecycle of
17+
/// custom documents.
18+
#[derive(Serialize, Deserialize, Debug, Clone)]
19+
#[serde(rename_all = "PascalCase")]
20+
pub struct CustomDocumentStateDTO {
21+
/// The URI of the document resource being edited.
22+
#[serde(with = "URLSerializationHelper")]
23+
pub URI:Url,
24+
25+
/// The view type of the custom editor responsible for this document.
26+
pub ViewType:String,
27+
28+
/// The identifier of the sidecar process where the custom editor provider
29+
/// lives.
30+
pub SidecarIdentifier:String,
31+
32+
/// A flag indicating if the document is currently editable by the user.
33+
pub IsEditable:bool,
34+
35+
/// An optional identifier for a backup copy of the file's content.
36+
#[serde(skip_serializing_if = "Option::is_none")]
37+
pub BackupIdentifier:Option<String>,
38+
39+
/// A map to store edit history or other versioning information.
40+
/// In a real implementation, this might hold a more structured edit type.
41+
pub Edits:HashMap<u32, serde_json::Value>,
42+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//! # DocumentStateDTO
2+
//!
3+
//! Defines the Data Transfer Object for storing the state of a single open
4+
//! text document in memory.
5+
6+
use serde::{Deserialize, Serialize};
7+
use serde_json::Value;
8+
use url::Url;
9+
10+
use super::RPCModelContentChangeDTO::RPCModelContentChangeDTO;
11+
use crate::ApplicationState::Internal::{
12+
AnalyzeTextLinesAndEOL,
13+
URLSerializationHelper, /* DetectFileEncodingFromBytes,
14+
* DetectLanguageIdentifierFromFilePath, */
15+
};
16+
17+
/// Represents the complete in-memory state of a single text document.
18+
#[derive(Serialize, Deserialize, Clone, Debug)]
19+
#[serde(rename_all = "PascalCase")]
20+
pub struct DocumentStateDTO {
21+
/// The unique resource identifier for this document.
22+
#[serde(with = "URLSerializationHelper")]
23+
pub URI:Url,
24+
/// The VS Code language identifier (e.g., "rust", "typescript").
25+
pub LanguageIdentifier:String,
26+
/// The version number, incremented on each change.
27+
pub Version:i64,
28+
/// The content of the document, split into lines.
29+
pub Lines:Vec<String>,
30+
/// The detected end-of-line sequence (e.g., `\n` or `\r\n`).
31+
pub EOL:String,
32+
/// A flag indicating if the in-memory version has unsaved changes.
33+
pub IsDirty:bool,
34+
/// The detected file encoding (e.g., "utf8").
35+
pub Encoding:String,
36+
}
37+
38+
impl DocumentStateDTO {
39+
/// Creates a new `DocumentStateDTO` from its initial content.
40+
pub fn Create(URI:Url, LanguageIdentifier:Option<String>, Content:String) -> Self {
41+
let (Lines, EOL) = AnalyzeTextLinesAndEOL(&Content);
42+
// A real implementation would have more robust language/encoding detection.
43+
let LanguageID = LanguageIdentifier.unwrap_or_else(|| "plaintext".to_string());
44+
let Encoding = "utf8".to_string(); // Stub for encoding detection
45+
46+
Self {
47+
URI,
48+
LanguageIdentifier:LanguageID,
49+
Version:1,
50+
Lines,
51+
EOL,
52+
IsDirty:false,
53+
Encoding,
54+
}
55+
}
56+
57+
/// Reconstructs the full text content of the document from its lines.
58+
pub fn GetText(&self) -> String { self.Lines.join(&self.EOL) }
59+
60+
/// Converts the struct to a `serde_json::Value`, useful for notifications.
61+
pub fn ToDTO(&self) -> Value { serde_json::to_value(self).unwrap_or(Value::Null) }
62+
63+
/// Applies a set of changes to the document.
64+
///
65+
/// This is a complex operation that simulates how a text buffer would be
66+
/// updated. For this implementation, it is simplified.
67+
pub fn ApplyChanges(&mut self, NewVersion:i64, ChangesValue:&Value) -> Result<(), String> {
68+
if NewVersion <= self.Version {
69+
return Ok(()); // Ignore stale changes
70+
}
71+
72+
let RPCChanges:Vec<RPCModelContentChangeDTO> = match serde_json::from_value(ChangesValue.clone()) {
73+
Ok(changes) => changes,
74+
Err(_) => {
75+
// Fallback for a full-content change, which is a common scenario.
76+
if let Some(FullText) = ChangesValue.as_str() {
77+
let (NewLines, NewEOL) = AnalyzeTextLinesAndEOL(FullText);
78+
self.Lines = NewLines;
79+
self.EOL = NewEOL;
80+
self.Version = NewVersion;
81+
self.IsDirty = true;
82+
return Ok(());
83+
}
84+
return Err(format!("Invalid RPCModelContentChangeDTO for {}", self.URI));
85+
},
86+
};
87+
88+
// A full implementation would require a rope data structure or complex logic
89+
// to apply deltas efficiently. We will log a warning and accept the new
90+
// version number, but the content will be out of sync until the next full
91+
// update.
92+
log::warn!(
93+
"Applying changes to {} by version bump only (delta application is a stub).",
94+
self.URI
95+
);
96+
97+
self.Version = NewVersion;
98+
self.IsDirty = true;
99+
Ok(())
100+
}
101+
}

0 commit comments

Comments
 (0)