pub mod builtin;
pub mod registry_integration;
#[cfg(not(feature = "std"))]
extern crate alloc;
use crate::core::{EditorDocument, Result};
use crate::events::DocumentEvent;
use core::fmt;
#[cfg(feature = "std")]
use std::collections::HashMap;
#[cfg(not(feature = "std"))]
use alloc::collections::BTreeMap as HashMap;
#[cfg(not(feature = "std"))]
use alloc::{
boxed::Box,
format,
string::{String, ToString},
vec::Vec,
};
#[cfg(feature = "multi-thread")]
use std::sync::Arc;
#[cfg(not(feature = "multi-thread"))]
use core::cell::RefCell;
#[cfg(all(not(feature = "multi-thread"), not(feature = "std")))]
use alloc::rc::Rc;
#[cfg(all(not(feature = "multi-thread"), feature = "std"))]
use std::rc::Rc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExtensionCapability {
TextProcessing,
SyntaxHighlighting,
CodeCompletion,
Linting,
FormatSupport,
CustomCommands,
UserInterface,
ToolIntegration,
EventHandling,
Performance,
}
impl ExtensionCapability {
pub fn description(&self) -> &'static str {
match self {
Self::TextProcessing => "Text processing and transformation",
Self::SyntaxHighlighting => "Syntax highlighting and theming",
Self::CodeCompletion => "Code completion and suggestions",
Self::Linting => "Linting and validation",
Self::FormatSupport => "Import/export format support",
Self::CustomCommands => "Custom commands and shortcuts",
Self::UserInterface => "UI enhancements and widgets",
Self::ToolIntegration => "External tool integration",
Self::EventHandling => "Custom event handling",
Self::Performance => "Performance monitoring",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtensionInfo {
pub name: String,
pub version: String,
pub author: String,
pub description: String,
pub capabilities: Vec<ExtensionCapability>,
pub dependencies: Vec<String>,
pub homepage: Option<String>,
pub license: Option<String>,
}
impl ExtensionInfo {
pub fn new(name: String, version: String, author: String, description: String) -> Self {
Self {
name,
version,
author,
description,
capabilities: Vec::new(),
dependencies: Vec::new(),
homepage: None,
license: None,
}
}
pub fn with_capability(mut self, capability: ExtensionCapability) -> Self {
self.capabilities.push(capability);
self
}
pub fn with_capabilities(mut self, capabilities: Vec<ExtensionCapability>) -> Self {
self.capabilities.extend(capabilities);
self
}
pub fn with_dependency(mut self, dependency: String) -> Self {
self.dependencies.push(dependency);
self
}
pub fn with_homepage(mut self, homepage: String) -> Self {
self.homepage = Some(homepage);
self
}
pub fn with_license(mut self, license: String) -> Self {
self.license = Some(license);
self
}
pub fn has_capability(&self, capability: &ExtensionCapability) -> bool {
self.capabilities.contains(capability)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtensionState {
Uninitialized,
Initializing,
Active,
Paused,
Error,
ShuttingDown,
Shutdown,
}
impl ExtensionState {
pub fn is_active(&self) -> bool {
matches!(self, Self::Active)
}
pub fn is_usable(&self) -> bool {
matches!(self, Self::Active | Self::Paused)
}
pub fn is_error(&self) -> bool {
matches!(self, Self::Error)
}
}
#[derive(Debug, Clone)]
pub struct ExtensionCommand {
pub id: String,
pub name: String,
pub description: String,
pub shortcut: Option<String>,
pub category: String,
pub requires_document: bool,
}
impl ExtensionCommand {
pub fn new(id: String, name: String, description: String) -> Self {
Self {
id,
name,
description,
shortcut: None,
category: "General".to_string(),
requires_document: true,
}
}
pub fn with_shortcut(mut self, shortcut: String) -> Self {
self.shortcut = Some(shortcut);
self
}
pub fn with_category(mut self, category: String) -> Self {
self.category = category;
self
}
pub fn requires_document(mut self, requires: bool) -> Self {
self.requires_document = requires;
self
}
}
pub trait ExtensionContext {
fn current_document(&self) -> Option<&EditorDocument>;
fn current_document_mut(&mut self) -> Option<&mut EditorDocument>;
fn send_event(&mut self, event: DocumentEvent) -> Result<()>;
fn get_config(&self, key: &str) -> Option<String>;
fn set_config(&mut self, key: String, value: String) -> Result<()>;
fn register_command(&mut self, command: ExtensionCommand) -> Result<()>;
fn show_message(&mut self, message: &str, level: MessageLevel) -> Result<()>;
fn get_extension_data(&self, extension_name: &str, key: &str) -> Option<String>;
fn set_extension_data(&mut self, key: String, value: String) -> Result<()>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageLevel {
Info,
Warning,
Error,
Success,
}
#[derive(Debug, Clone)]
pub struct ExtensionResult {
pub success: bool,
pub message: Option<String>,
pub data: HashMap<String, String>,
}
impl ExtensionResult {
pub fn success() -> Self {
Self {
success: true,
message: None,
data: HashMap::new(),
}
}
pub fn success_with_message(message: String) -> Self {
Self {
success: true,
message: Some(message),
data: HashMap::new(),
}
}
pub fn failure(message: String) -> Self {
Self {
success: false,
message: Some(message),
data: HashMap::new(),
}
}
pub fn with_data(mut self, key: String, value: String) -> Self {
self.data.insert(key, value);
self
}
}
pub trait EditorExtension: Send + Sync {
fn info(&self) -> &ExtensionInfo;
fn initialize(&mut self, context: &mut dyn ExtensionContext) -> Result<()>;
fn shutdown(&mut self, context: &mut dyn ExtensionContext) -> Result<()>;
fn state(&self) -> ExtensionState;
fn execute_command(
&mut self,
command_id: &str,
args: &HashMap<String, String>,
context: &mut dyn ExtensionContext,
) -> Result<ExtensionResult>;
fn commands(&self) -> Vec<ExtensionCommand> {
Vec::new()
}
fn handle_event(
&mut self,
_event: &DocumentEvent,
_context: &mut dyn ExtensionContext,
) -> Result<()> {
Ok(())
}
fn config_schema(&self) -> HashMap<String, String> {
HashMap::new()
}
fn validate_config(&self, _config: &HashMap<String, String>) -> Result<()> {
Ok(())
}
fn pause(&mut self) -> Result<()> {
Ok(())
}
fn resume(&mut self) -> Result<()> {
Ok(())
}
fn get_data(&self, _key: &str) -> Option<String> {
None
}
fn set_data(&mut self, _key: String, _value: String) -> Result<()> {
Ok(())
}
}
struct ExtensionManagerInner {
extensions: HashMap<String, Box<dyn EditorExtension>>,
extension_states: HashMap<String, ExtensionState>,
commands: HashMap<String, (String, ExtensionCommand)>,
config: HashMap<String, String>,
extension_data: HashMap<String, HashMap<String, String>>,
#[cfg(feature = "std")]
#[allow(dead_code)]
event_tx: EventSender,
#[allow(dead_code)]
message_handler: Box<dyn MessageHandler>,
}
#[cfg(feature = "multi-thread")]
use parking_lot::Mutex;
pub struct ExtensionManager {
#[cfg(feature = "multi-thread")]
inner: Arc<Mutex<ExtensionManagerInner>>,
#[cfg(not(feature = "multi-thread"))]
inner: RefCell<ExtensionManagerInner>,
}
#[cfg(feature = "multi-thread")]
impl Clone for ExtensionManager {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl fmt::Debug for ExtensionManager {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "multi-thread")]
{
let inner = self.inner.lock();
f.debug_struct("ExtensionManager")
.field("extension_states", &inner.extension_states)
.field("commands", &inner.commands.keys().collect::<Vec<_>>())
.field("config", &inner.config)
.field("extension_data", &inner.extension_data)
.field("extensions", &"<HashMap<String, Box<dyn EditorExtension>>>")
.finish()
}
#[cfg(not(feature = "multi-thread"))]
{
let inner = self.inner.borrow();
f.debug_struct("ExtensionManager")
.field("extension_states", &inner.extension_states)
.field("commands", &inner.commands.keys().collect::<Vec<_>>())
.field("config", &inner.config)
.field("extension_data", &inner.extension_data)
.field("extensions", &"<HashMap<String, Box<dyn EditorExtension>>>")
.finish()
}
}
}
impl ExtensionManagerInner {
fn new() -> Self {
#[cfg(feature = "std")]
let (tx, _rx) = mpsc::channel();
Self {
extensions: HashMap::new(),
extension_states: HashMap::new(),
commands: HashMap::new(),
config: HashMap::new(),
extension_data: HashMap::new(),
#[cfg(feature = "std")]
event_tx: tx,
#[cfg(feature = "std")]
message_handler: Box::new(StdMessageHandler),
#[cfg(not(feature = "std"))]
message_handler: Box::new(NoOpMessageHandler),
}
}
}
impl ExtensionManager {
#[cfg(feature = "multi-thread")]
fn with_inner_mut<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut ExtensionManagerInner) -> R,
{
let mut inner = self.inner.lock();
f(&mut inner)
}
#[cfg(feature = "multi-thread")]
fn with_inner<F, R>(&self, f: F) -> R
where
F: FnOnce(&ExtensionManagerInner) -> R,
{
let inner = self.inner.lock();
f(&inner)
}
#[cfg(not(feature = "multi-thread"))]
fn with_inner_mut<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut ExtensionManagerInner) -> R,
{
let mut inner = self.inner.borrow_mut();
f(&mut inner)
}
#[cfg(not(feature = "multi-thread"))]
fn with_inner<F, R>(&self, f: F) -> R
where
F: FnOnce(&ExtensionManagerInner) -> R,
{
let inner = self.inner.borrow();
f(&inner)
}
pub fn new() -> Self {
#[cfg(feature = "multi-thread")]
{
Self {
inner: Arc::new(Mutex::new(ExtensionManagerInner::new())),
}
}
#[cfg(not(feature = "multi-thread"))]
{
Self {
inner: RefCell::new(ExtensionManagerInner::new()),
}
}
}
#[cfg(feature = "std")]
pub fn with_event_channel(
event_tx: EventSender,
message_handler: Box<dyn MessageHandler>,
) -> Self {
let inner = ExtensionManagerInner {
extensions: HashMap::new(),
extension_states: HashMap::new(),
commands: HashMap::new(),
config: HashMap::new(),
extension_data: HashMap::new(),
event_tx,
message_handler,
};
#[cfg(feature = "multi-thread")]
{
Self {
inner: Arc::new(Mutex::new(inner)),
}
}
#[cfg(not(feature = "multi-thread"))]
{
Self {
inner: RefCell::new(inner),
}
}
}
pub fn create_context<'a>(
&'a mut self,
extension_name: String,
document: Option<&'a mut EditorDocument>,
) -> Result<Box<dyn ExtensionContext + 'a>> {
#[cfg(feature = "multi-thread")]
{
Ok(Box::new(EditorContext {
document,
manager: self.clone(),
extension_name,
}))
}
#[cfg(not(feature = "multi-thread"))]
{
let config_clone = self.inner.borrow().config.clone();
let shared_config = Rc::new(RefCell::new(config_clone));
Ok(Box::new(EditorContext {
document,
manager: self,
manager_mut_state: shared_config,
extension_name,
}))
}
}
pub fn load_extension(&mut self, extension: Box<dyn EditorExtension>) -> Result<()> {
let extension_name = extension.info().name.clone();
let dependencies = extension.info().dependencies.clone();
let has_deps = self.with_inner(|inner| {
dependencies
.iter()
.all(|dep| inner.extension_states.contains_key(dep))
});
if !has_deps {
return Err(crate::core::EditorError::CommandFailed {
message: format!("Extension '{extension_name}' has unmet dependencies"),
});
}
self.with_inner_mut(|inner| {
if inner.extensions.contains_key(&extension_name) {
return Err(crate::core::EditorError::CommandFailed {
message: format!("Extension '{extension_name}' is already loaded"),
});
}
inner.extensions.insert(extension_name.clone(), extension);
inner
.extension_states
.insert(extension_name.clone(), ExtensionState::Uninitialized);
Ok(())
})
}
pub fn initialize_extension(
&mut self,
extension_name: &str,
context: &mut dyn ExtensionContext,
) -> Result<()> {
self.with_inner_mut(|inner| {
inner
.extension_states
.insert(extension_name.to_string(), ExtensionState::Initializing);
if let Some(extension) = inner.extensions.get_mut(extension_name) {
match extension.initialize(context) {
Ok(()) => {
inner
.extension_states
.insert(extension_name.to_string(), ExtensionState::Active);
for command in extension.commands() {
inner
.commands
.insert(command.id.clone(), (extension_name.to_string(), command));
}
Ok(())
}
Err(e) => {
inner
.extension_states
.insert(extension_name.to_string(), ExtensionState::Error);
Err(e)
}
}
} else {
Err(crate::core::EditorError::CommandFailed {
message: format!("Extension '{extension_name}' not found"),
})
}
})
}
pub fn unload_extension(
&mut self,
extension_name: &str,
context: &mut dyn ExtensionContext,
) -> Result<()> {
self.shutdown_extension(extension_name, context)?;
self.with_inner_mut(|inner| {
inner.extensions.remove(extension_name);
inner.extension_states.remove(extension_name);
inner
.commands
.retain(|_, (ext_name, _)| ext_name != extension_name);
inner.extension_data.remove(extension_name);
});
Ok(())
}
fn shutdown_extension(
&mut self,
extension_name: &str,
context: &mut dyn ExtensionContext,
) -> Result<()> {
self.with_inner_mut(|inner| {
inner
.extension_states
.insert(extension_name.to_string(), ExtensionState::ShuttingDown);
if let Some(extension) = inner.extensions.get_mut(extension_name) {
extension.shutdown(context)?;
inner
.extension_states
.insert(extension_name.to_string(), ExtensionState::Shutdown);
}
Ok(())
})
}
pub fn execute_command(
&mut self,
command_id: &str,
args: &HashMap<String, String>,
context: &mut dyn ExtensionContext,
) -> Result<ExtensionResult> {
let extension_name = self
.with_inner(|inner| inner.commands.get(command_id).map(|(name, _)| name.clone()))
.ok_or_else(|| crate::core::EditorError::CommandFailed {
message: format!("Command '{command_id}' not found"),
})?;
self.with_inner_mut(|inner| {
if let Some(extension) = inner.extensions.get_mut(&extension_name) {
extension.execute_command(command_id, args, context)
} else {
Err(crate::core::EditorError::CommandFailed {
message: format!("Extension '{extension_name}' not found"),
})
}
})
}
pub fn list_extensions(&self) -> Vec<String> {
self.with_inner(|inner| inner.extensions.keys().cloned().collect())
}
pub fn get_extension_state(&self, extension_name: &str) -> Option<ExtensionState> {
self.with_inner(|inner| inner.extension_states.get(extension_name).copied())
}
pub fn list_commands(&self) -> Vec<String> {
self.with_inner(|inner| inner.commands.keys().cloned().collect())
}
pub fn get_config(&self, key: &str) -> Option<String> {
self.with_inner(|inner| inner.config.get(key).cloned())
}
pub fn set_config(&mut self, key: String, value: String) {
self.with_inner_mut(|inner| {
inner.config.insert(key, value);
});
}
pub fn get_extension_data(&self, extension_name: &str, key: &str) -> Option<String> {
self.with_inner(|inner| {
inner
.extension_data
.get(extension_name)
.and_then(|data| data.get(key))
.cloned()
})
}
pub fn set_extension_data(&mut self, extension_name: String, key: String, value: String) {
self.with_inner_mut(|inner| {
inner
.extension_data
.entry(extension_name)
.or_default()
.insert(key, value);
});
}
}
impl Default for ExtensionManager {
fn default() -> Self {
Self::new()
}
}
pub trait MessageHandler: Send + Sync {
fn show(&mut self, message: &str, level: MessageLevel) -> Result<()>;
}
#[cfg(feature = "std")]
pub struct StdMessageHandler;
#[cfg(feature = "std")]
impl MessageHandler for StdMessageHandler {
fn show(&mut self, message: &str, level: MessageLevel) -> Result<()> {
match level {
MessageLevel::Error => eprintln!("[ERROR] {message}"),
MessageLevel::Warning => eprintln!("[WARN] {message}"),
MessageLevel::Info => println!("[INFO] {message}"),
MessageLevel::Success => println!("[SUCCESS] {message}"),
}
Ok(())
}
}
#[cfg(not(feature = "std"))]
pub struct NoOpMessageHandler;
#[cfg(not(feature = "std"))]
impl MessageHandler for NoOpMessageHandler {
fn show(&mut self, _message: &str, _level: MessageLevel) -> Result<()> {
Ok(())
}
}
#[cfg(feature = "std")]
use std::sync::mpsc::{self, Sender};
#[cfg(feature = "std")]
pub type EventSender = Sender<DocumentEvent>;
#[cfg(not(feature = "std"))]
pub type EventSender = ();
pub struct EditorContext<'a> {
pub document: Option<&'a mut EditorDocument>,
#[cfg(feature = "multi-thread")]
pub manager: ExtensionManager,
#[cfg(not(feature = "multi-thread"))]
pub manager: &'a mut ExtensionManager,
#[cfg(not(feature = "multi-thread"))]
pub manager_mut_state: alloc::rc::Rc<core::cell::RefCell<HashMap<String, String>>>,
pub extension_name: String,
}
impl<'a> ExtensionContext for EditorContext<'a> {
fn current_document(&self) -> Option<&EditorDocument> {
self.document.as_deref()
}
fn current_document_mut(&mut self) -> Option<&mut EditorDocument> {
self.document.as_deref_mut()
}
fn send_event(&mut self, _event: DocumentEvent) -> Result<()> {
#[cfg(feature = "std")]
{
eprintln!("Extension {} sent event: {:?}", self.extension_name, _event);
}
Ok(())
}
fn get_config(&self, key: &str) -> Option<String> {
#[cfg(feature = "multi-thread")]
{
self.manager.get_config(key)
}
#[cfg(not(feature = "multi-thread"))]
{
self.manager.get_config(key)
}
}
fn set_config(&mut self, key: String, value: String) -> Result<()> {
#[cfg(feature = "multi-thread")]
{
self.manager.set_config(key.clone(), value.clone());
}
#[cfg(not(feature = "multi-thread"))]
{
self.manager_mut_state.borrow_mut().insert(key, value);
}
Ok(())
}
fn register_command(&mut self, _command: ExtensionCommand) -> Result<()> {
#[cfg(feature = "std")]
{
eprintln!(
"Extension {} registered command: {}",
self.extension_name, _command.id
);
}
Ok(())
}
fn show_message(&mut self, _message: &str, _level: MessageLevel) -> Result<()> {
#[cfg(feature = "std")]
{
match _level {
MessageLevel::Info => eprintln!("[INFO] {}: {}", self.extension_name, _message),
MessageLevel::Warning => eprintln!("[WARN] {}: {}", self.extension_name, _message),
MessageLevel::Error => eprintln!("[ERROR] {}: {}", self.extension_name, _message),
MessageLevel::Success => {
eprintln!("[SUCCESS] {}: {}", self.extension_name, _message)
}
}
}
Ok(())
}
fn get_extension_data(&self, extension_name: &str, key: &str) -> Option<String> {
self.manager.get_extension_data(extension_name, key)
}
fn set_extension_data(&mut self, key: String, value: String) -> Result<()> {
self.manager
.set_extension_data(self.extension_name.clone(), key, value);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::{string::ToString, vec};
#[test]
fn extension_info_creation() {
let info = ExtensionInfo::new(
"test-extension".to_string(),
"1.0.0".to_string(),
"Test Author".to_string(),
"A test extension".to_string(),
)
.with_capability(ExtensionCapability::TextProcessing)
.with_dependency("core-extension".to_string())
.with_homepage("https://example.com".to_string())
.with_license("MIT".to_string());
assert_eq!(info.name, "test-extension");
assert_eq!(info.version, "1.0.0");
assert!(info.has_capability(&ExtensionCapability::TextProcessing));
assert_eq!(info.dependencies.len(), 1);
assert_eq!(info.homepage, Some("https://example.com".to_string()));
assert_eq!(info.license, Some("MIT".to_string()));
}
#[test]
fn extension_capability_description() {
let capability = ExtensionCapability::TextProcessing;
assert_eq!(
capability.description(),
"Text processing and transformation"
);
}
#[test]
fn extension_state_checks() {
let state = ExtensionState::Active;
assert!(state.is_active());
assert!(state.is_usable());
assert!(!state.is_error());
let error_state = ExtensionState::Error;
assert!(!error_state.is_active());
assert!(!error_state.is_usable());
assert!(error_state.is_error());
}
#[test]
fn extension_command_creation() {
let command = ExtensionCommand::new(
"test-command".to_string(),
"Test Command".to_string(),
"A test command".to_string(),
)
.with_shortcut("Ctrl+T".to_string())
.with_category("Testing".to_string())
.requires_document(false);
assert_eq!(command.id, "test-command");
assert_eq!(command.shortcut, Some("Ctrl+T".to_string()));
assert_eq!(command.category, "Testing");
assert!(!command.requires_document);
}
#[test]
fn extension_result_creation() {
let success = ExtensionResult::success_with_message("Success!".to_string())
.with_data("key".to_string(), "value".to_string());
assert!(success.success);
assert_eq!(success.message, Some("Success!".to_string()));
assert_eq!(success.data.get("key"), Some(&"value".to_string()));
let failure = ExtensionResult::failure("Failed!".to_string());
assert!(!failure.success);
assert_eq!(failure.message, Some("Failed!".to_string()));
}
#[test]
fn extension_manager_creation() {
let manager = ExtensionManager::new();
assert_eq!(manager.list_extensions().len(), 0);
assert_eq!(manager.list_commands().len(), 0);
}
#[test]
fn extension_manager_config() {
let mut manager = ExtensionManager::new();
manager.set_config("test_key".to_string(), "test_value".to_string());
assert_eq!(
manager.get_config("test_key"),
Some("test_value".to_string())
);
assert_eq!(manager.get_config("nonexistent"), None);
}
#[test]
fn extension_manager_data() {
let mut manager = ExtensionManager::new();
manager.set_extension_data("ext1".to_string(), "key".to_string(), "value".to_string());
assert_eq!(
manager.get_extension_data("ext1", "key"),
Some("value".to_string())
);
assert_eq!(manager.get_extension_data("ext1", "nonexistent"), None);
assert_eq!(manager.get_extension_data("ext2", "key"), None);
}
struct TestExtension {
info: ExtensionInfo,
state: ExtensionState,
data: HashMap<String, String>,
}
impl TestExtension {
fn new(name: &str) -> Self {
Self {
info: ExtensionInfo::new(
name.to_string(),
"1.0.0".to_string(),
"Test".to_string(),
"Test extension".to_string(),
),
state: ExtensionState::Uninitialized,
data: HashMap::new(),
}
}
}
impl EditorExtension for TestExtension {
fn info(&self) -> &ExtensionInfo {
&self.info
}
fn initialize(&mut self, _context: &mut dyn ExtensionContext) -> Result<()> {
self.state = ExtensionState::Active;
Ok(())
}
fn shutdown(&mut self, _context: &mut dyn ExtensionContext) -> Result<()> {
self.state = ExtensionState::Shutdown;
Ok(())
}
fn state(&self) -> ExtensionState {
self.state
}
fn execute_command(
&mut self,
command_id: &str,
_args: &HashMap<String, String>,
_context: &mut dyn ExtensionContext,
) -> Result<ExtensionResult> {
match command_id {
"test-command" => Ok(ExtensionResult::success_with_message(
"Command executed".to_string(),
)),
_ => Ok(ExtensionResult::failure("Unknown command".to_string())),
}
}
fn commands(&self) -> Vec<ExtensionCommand> {
vec![ExtensionCommand::new(
"test-command".to_string(),
"Test Command".to_string(),
"A test command".to_string(),
)]
}
fn get_data(&self, key: &str) -> Option<String> {
self.data.get(key).cloned()
}
fn set_data(&mut self, key: String, value: String) -> Result<()> {
self.data.insert(key, value);
Ok(())
}
}
#[test]
fn extension_manager_lifecycle() {
let mut manager = ExtensionManager::new();
let _doc = EditorDocument::new();
let extension = Box::new(TestExtension::new("test-ext"));
manager.load_extension(extension).unwrap();
assert_eq!(manager.list_extensions().len(), 1);
assert_eq!(
manager.get_extension_state("test-ext"),
Some(ExtensionState::Uninitialized)
);
{
}
let extension_exists = manager.list_extensions().contains(&"test-ext".to_string());
assert!(extension_exists);
}
#[test]
fn editor_context() {
let mut manager = ExtensionManager::new();
manager.set_config("test".to_string(), "value".to_string());
assert_eq!(manager.get_config("test"), Some("value".to_string()));
manager.set_extension_data("default".to_string(), "key".to_string(), "data".to_string());
assert_eq!(
manager.get_extension_data("default", "key"),
Some("data".to_string())
);
}
}