1pub mod builtin;
8pub mod registry_integration;
9
10#[cfg(not(feature = "std"))]
11extern crate alloc;
12
13use crate::core::{EditorDocument, Result};
14use crate::events::DocumentEvent;
15use core::fmt;
16
17#[cfg(feature = "std")]
18use std::collections::HashMap;
19
20#[cfg(not(feature = "std"))]
21use alloc::collections::BTreeMap as HashMap;
22
23#[cfg(not(feature = "std"))]
24use alloc::{
25 boxed::Box,
26 format,
27 string::{String, ToString},
28 vec::Vec,
29};
30
31#[cfg(feature = "multi-thread")]
32use std::sync::Arc;
33
34#[cfg(not(feature = "multi-thread"))]
35use core::cell::RefCell;
36
37#[cfg(all(not(feature = "multi-thread"), not(feature = "std")))]
38use alloc::rc::Rc;
39
40#[cfg(all(not(feature = "multi-thread"), feature = "std"))]
41use std::rc::Rc;
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum ExtensionCapability {
46 TextProcessing,
48 SyntaxHighlighting,
50 CodeCompletion,
52 Linting,
54 FormatSupport,
56 CustomCommands,
58 UserInterface,
60 ToolIntegration,
62 EventHandling,
64 Performance,
66}
67
68impl ExtensionCapability {
69 pub fn description(&self) -> &'static str {
71 match self {
72 Self::TextProcessing => "Text processing and transformation",
73 Self::SyntaxHighlighting => "Syntax highlighting and theming",
74 Self::CodeCompletion => "Code completion and suggestions",
75 Self::Linting => "Linting and validation",
76 Self::FormatSupport => "Import/export format support",
77 Self::CustomCommands => "Custom commands and shortcuts",
78 Self::UserInterface => "UI enhancements and widgets",
79 Self::ToolIntegration => "External tool integration",
80 Self::EventHandling => "Custom event handling",
81 Self::Performance => "Performance monitoring",
82 }
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct ExtensionInfo {
89 pub name: String,
91 pub version: String,
93 pub author: String,
95 pub description: String,
97 pub capabilities: Vec<ExtensionCapability>,
99 pub dependencies: Vec<String>,
101 pub homepage: Option<String>,
103 pub license: Option<String>,
105}
106
107impl ExtensionInfo {
108 pub fn new(name: String, version: String, author: String, description: String) -> Self {
110 Self {
111 name,
112 version,
113 author,
114 description,
115 capabilities: Vec::new(),
116 dependencies: Vec::new(),
117 homepage: None,
118 license: None,
119 }
120 }
121
122 pub fn with_capability(mut self, capability: ExtensionCapability) -> Self {
124 self.capabilities.push(capability);
125 self
126 }
127
128 pub fn with_capabilities(mut self, capabilities: Vec<ExtensionCapability>) -> Self {
130 self.capabilities.extend(capabilities);
131 self
132 }
133
134 pub fn with_dependency(mut self, dependency: String) -> Self {
136 self.dependencies.push(dependency);
137 self
138 }
139
140 pub fn with_homepage(mut self, homepage: String) -> Self {
142 self.homepage = Some(homepage);
143 self
144 }
145
146 pub fn with_license(mut self, license: String) -> Self {
148 self.license = Some(license);
149 self
150 }
151
152 pub fn has_capability(&self, capability: &ExtensionCapability) -> bool {
154 self.capabilities.contains(capability)
155 }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum ExtensionState {
161 Uninitialized,
163 Initializing,
165 Active,
167 Paused,
169 Error,
171 ShuttingDown,
173 Shutdown,
175}
176
177impl ExtensionState {
178 pub fn is_active(&self) -> bool {
180 matches!(self, Self::Active)
181 }
182
183 pub fn is_usable(&self) -> bool {
185 matches!(self, Self::Active | Self::Paused)
186 }
187
188 pub fn is_error(&self) -> bool {
190 matches!(self, Self::Error)
191 }
192}
193
194#[derive(Debug, Clone)]
196pub struct ExtensionCommand {
197 pub id: String,
199 pub name: String,
201 pub description: String,
203 pub shortcut: Option<String>,
205 pub category: String,
207 pub requires_document: bool,
209}
210
211impl ExtensionCommand {
212 pub fn new(id: String, name: String, description: String) -> Self {
214 Self {
215 id,
216 name,
217 description,
218 shortcut: None,
219 category: "General".to_string(),
220 requires_document: true,
221 }
222 }
223
224 pub fn with_shortcut(mut self, shortcut: String) -> Self {
226 self.shortcut = Some(shortcut);
227 self
228 }
229
230 pub fn with_category(mut self, category: String) -> Self {
232 self.category = category;
233 self
234 }
235
236 pub fn requires_document(mut self, requires: bool) -> Self {
238 self.requires_document = requires;
239 self
240 }
241}
242
243pub trait ExtensionContext {
245 fn current_document(&self) -> Option<&EditorDocument>;
247
248 fn current_document_mut(&mut self) -> Option<&mut EditorDocument>;
250
251 fn send_event(&mut self, event: DocumentEvent) -> Result<()>;
253
254 fn get_config(&self, key: &str) -> Option<String>;
256
257 fn set_config(&mut self, key: String, value: String) -> Result<()>;
259
260 fn register_command(&mut self, command: ExtensionCommand) -> Result<()>;
262
263 fn show_message(&mut self, message: &str, level: MessageLevel) -> Result<()>;
265
266 fn get_extension_data(&self, extension_name: &str, key: &str) -> Option<String>;
268
269 fn set_extension_data(&mut self, key: String, value: String) -> Result<()>;
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275pub enum MessageLevel {
276 Info,
278 Warning,
280 Error,
282 Success,
284}
285
286#[derive(Debug, Clone)]
288pub struct ExtensionResult {
289 pub success: bool,
291 pub message: Option<String>,
293 pub data: HashMap<String, String>,
295}
296
297impl ExtensionResult {
298 pub fn success() -> Self {
300 Self {
301 success: true,
302 message: None,
303 data: HashMap::new(),
304 }
305 }
306
307 pub fn success_with_message(message: String) -> Self {
309 Self {
310 success: true,
311 message: Some(message),
312 data: HashMap::new(),
313 }
314 }
315
316 pub fn failure(message: String) -> Self {
318 Self {
319 success: false,
320 message: Some(message),
321 data: HashMap::new(),
322 }
323 }
324
325 pub fn with_data(mut self, key: String, value: String) -> Self {
327 self.data.insert(key, value);
328 self
329 }
330}
331
332pub trait EditorExtension: Send + Sync {
334 fn info(&self) -> &ExtensionInfo;
336
337 fn initialize(&mut self, context: &mut dyn ExtensionContext) -> Result<()>;
339
340 fn shutdown(&mut self, context: &mut dyn ExtensionContext) -> Result<()>;
342
343 fn state(&self) -> ExtensionState;
345
346 fn execute_command(
348 &mut self,
349 command_id: &str,
350 args: &HashMap<String, String>,
351 context: &mut dyn ExtensionContext,
352 ) -> Result<ExtensionResult>;
353
354 fn commands(&self) -> Vec<ExtensionCommand> {
356 Vec::new()
357 }
358
359 fn handle_event(
361 &mut self,
362 _event: &DocumentEvent,
363 _context: &mut dyn ExtensionContext,
364 ) -> Result<()> {
365 Ok(())
367 }
368
369 fn config_schema(&self) -> HashMap<String, String> {
371 HashMap::new()
372 }
373
374 fn validate_config(&self, _config: &HashMap<String, String>) -> Result<()> {
376 Ok(())
377 }
378
379 fn pause(&mut self) -> Result<()> {
381 Ok(())
382 }
383
384 fn resume(&mut self) -> Result<()> {
386 Ok(())
387 }
388
389 fn get_data(&self, _key: &str) -> Option<String> {
391 None
392 }
393
394 fn set_data(&mut self, _key: String, _value: String) -> Result<()> {
396 Ok(())
397 }
398}
399
400struct ExtensionManagerInner {
403 extensions: HashMap<String, Box<dyn EditorExtension>>,
405
406 extension_states: HashMap<String, ExtensionState>,
408
409 commands: HashMap<String, (String, ExtensionCommand)>, config: HashMap<String, String>,
414
415 extension_data: HashMap<String, HashMap<String, String>>,
417
418 #[cfg(feature = "std")]
420 #[allow(dead_code)]
421 event_tx: EventSender,
422
423 #[allow(dead_code)]
425 message_handler: Box<dyn MessageHandler>,
426}
427
428#[cfg(feature = "multi-thread")]
430use parking_lot::Mutex;
431
432pub struct ExtensionManager {
434 #[cfg(feature = "multi-thread")]
435 inner: Arc<Mutex<ExtensionManagerInner>>,
436 #[cfg(not(feature = "multi-thread"))]
437 inner: RefCell<ExtensionManagerInner>,
438}
439
440#[cfg(feature = "multi-thread")]
442impl Clone for ExtensionManager {
443 fn clone(&self) -> Self {
444 Self {
445 inner: self.inner.clone(),
446 }
447 }
448}
449
450impl fmt::Debug for ExtensionManager {
454 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455 #[cfg(feature = "multi-thread")]
456 {
457 let inner = self.inner.lock();
458 f.debug_struct("ExtensionManager")
459 .field("extension_states", &inner.extension_states)
460 .field("commands", &inner.commands.keys().collect::<Vec<_>>())
461 .field("config", &inner.config)
462 .field("extension_data", &inner.extension_data)
463 .field("extensions", &"<HashMap<String, Box<dyn EditorExtension>>>")
464 .finish()
465 }
466 #[cfg(not(feature = "multi-thread"))]
467 {
468 let inner = self.inner.borrow();
469 f.debug_struct("ExtensionManager")
470 .field("extension_states", &inner.extension_states)
471 .field("commands", &inner.commands.keys().collect::<Vec<_>>())
472 .field("config", &inner.config)
473 .field("extension_data", &inner.extension_data)
474 .field("extensions", &"<HashMap<String, Box<dyn EditorExtension>>>")
475 .finish()
476 }
477 }
478}
479
480impl ExtensionManagerInner {
481 fn new() -> Self {
483 #[cfg(feature = "std")]
484 let (tx, _rx) = mpsc::channel();
485
486 Self {
487 extensions: HashMap::new(),
488 extension_states: HashMap::new(),
489 commands: HashMap::new(),
490 config: HashMap::new(),
491 extension_data: HashMap::new(),
492 #[cfg(feature = "std")]
493 event_tx: tx,
494 #[cfg(feature = "std")]
495 message_handler: Box::new(StdMessageHandler),
496 #[cfg(not(feature = "std"))]
497 message_handler: Box::new(NoOpMessageHandler),
498 }
499 }
500}
501
502impl ExtensionManager {
503 #[cfg(feature = "multi-thread")]
505 fn with_inner_mut<F, R>(&self, f: F) -> R
506 where
507 F: FnOnce(&mut ExtensionManagerInner) -> R,
508 {
509 let mut inner = self.inner.lock();
510 f(&mut inner)
511 }
512
513 #[cfg(feature = "multi-thread")]
515 fn with_inner<F, R>(&self, f: F) -> R
516 where
517 F: FnOnce(&ExtensionManagerInner) -> R,
518 {
519 let inner = self.inner.lock();
520 f(&inner)
521 }
522
523 #[cfg(not(feature = "multi-thread"))]
525 fn with_inner_mut<F, R>(&self, f: F) -> R
526 where
527 F: FnOnce(&mut ExtensionManagerInner) -> R,
528 {
529 let mut inner = self.inner.borrow_mut();
530 f(&mut inner)
531 }
532
533 #[cfg(not(feature = "multi-thread"))]
535 fn with_inner<F, R>(&self, f: F) -> R
536 where
537 F: FnOnce(&ExtensionManagerInner) -> R,
538 {
539 let inner = self.inner.borrow();
540 f(&inner)
541 }
542
543 pub fn new() -> Self {
545 #[cfg(feature = "multi-thread")]
546 {
547 Self {
548 inner: Arc::new(Mutex::new(ExtensionManagerInner::new())),
549 }
550 }
551 #[cfg(not(feature = "multi-thread"))]
552 {
553 Self {
554 inner: RefCell::new(ExtensionManagerInner::new()),
555 }
556 }
557 }
558
559 #[cfg(feature = "std")]
561 pub fn with_event_channel(
562 event_tx: EventSender,
563 message_handler: Box<dyn MessageHandler>,
564 ) -> Self {
565 let inner = ExtensionManagerInner {
566 extensions: HashMap::new(),
567 extension_states: HashMap::new(),
568 commands: HashMap::new(),
569 config: HashMap::new(),
570 extension_data: HashMap::new(),
571 event_tx,
572 message_handler,
573 };
574
575 #[cfg(feature = "multi-thread")]
576 {
577 Self {
578 inner: Arc::new(Mutex::new(inner)),
579 }
580 }
581 #[cfg(not(feature = "multi-thread"))]
582 {
583 Self {
584 inner: RefCell::new(inner),
585 }
586 }
587 }
588
589 pub fn create_context<'a>(
591 &'a mut self,
592 extension_name: String,
593 document: Option<&'a mut EditorDocument>,
594 ) -> Result<Box<dyn ExtensionContext + 'a>> {
595 #[cfg(feature = "multi-thread")]
596 {
597 Ok(Box::new(EditorContext {
598 document,
599 manager: self.clone(),
600 extension_name,
601 }))
602 }
603
604 #[cfg(not(feature = "multi-thread"))]
605 {
606 let config_clone = self.inner.borrow().config.clone();
608 let shared_config = Rc::new(RefCell::new(config_clone));
609
610 Ok(Box::new(EditorContext {
611 document,
612 manager: self,
613 manager_mut_state: shared_config,
614 extension_name,
615 }))
616 }
617 }
618
619 pub fn load_extension(&mut self, extension: Box<dyn EditorExtension>) -> Result<()> {
621 let extension_name = extension.info().name.clone();
622 let dependencies = extension.info().dependencies.clone();
623
624 let has_deps = self.with_inner(|inner| {
626 dependencies
627 .iter()
628 .all(|dep| inner.extension_states.contains_key(dep))
629 });
630
631 if !has_deps {
632 return Err(crate::core::EditorError::CommandFailed {
633 message: format!("Extension '{extension_name}' has unmet dependencies"),
634 });
635 }
636
637 self.with_inner_mut(|inner| {
638 if inner.extensions.contains_key(&extension_name) {
639 return Err(crate::core::EditorError::CommandFailed {
640 message: format!("Extension '{extension_name}' is already loaded"),
641 });
642 }
643
644 inner.extensions.insert(extension_name.clone(), extension);
645 inner
646 .extension_states
647 .insert(extension_name.clone(), ExtensionState::Uninitialized);
648 Ok(())
649 })
650 }
651
652 pub fn initialize_extension(
654 &mut self,
655 extension_name: &str,
656 context: &mut dyn ExtensionContext,
657 ) -> Result<()> {
658 self.with_inner_mut(|inner| {
659 inner
660 .extension_states
661 .insert(extension_name.to_string(), ExtensionState::Initializing);
662
663 if let Some(extension) = inner.extensions.get_mut(extension_name) {
664 match extension.initialize(context) {
665 Ok(()) => {
666 inner
667 .extension_states
668 .insert(extension_name.to_string(), ExtensionState::Active);
669
670 for command in extension.commands() {
672 inner
673 .commands
674 .insert(command.id.clone(), (extension_name.to_string(), command));
675 }
676
677 Ok(())
678 }
679 Err(e) => {
680 inner
681 .extension_states
682 .insert(extension_name.to_string(), ExtensionState::Error);
683 Err(e)
684 }
685 }
686 } else {
687 Err(crate::core::EditorError::CommandFailed {
688 message: format!("Extension '{extension_name}' not found"),
689 })
690 }
691 })
692 }
693
694 pub fn unload_extension(
696 &mut self,
697 extension_name: &str,
698 context: &mut dyn ExtensionContext,
699 ) -> Result<()> {
700 self.shutdown_extension(extension_name, context)?;
702
703 self.with_inner_mut(|inner| {
704 inner.extensions.remove(extension_name);
706 inner.extension_states.remove(extension_name);
707
708 inner
710 .commands
711 .retain(|_, (ext_name, _)| ext_name != extension_name);
712
713 inner.extension_data.remove(extension_name);
715 });
716
717 Ok(())
718 }
719
720 fn shutdown_extension(
722 &mut self,
723 extension_name: &str,
724 context: &mut dyn ExtensionContext,
725 ) -> Result<()> {
726 self.with_inner_mut(|inner| {
727 inner
728 .extension_states
729 .insert(extension_name.to_string(), ExtensionState::ShuttingDown);
730
731 if let Some(extension) = inner.extensions.get_mut(extension_name) {
732 extension.shutdown(context)?;
733 inner
734 .extension_states
735 .insert(extension_name.to_string(), ExtensionState::Shutdown);
736 }
737 Ok(())
738 })
739 }
740
741 pub fn execute_command(
743 &mut self,
744 command_id: &str,
745 args: &HashMap<String, String>,
746 context: &mut dyn ExtensionContext,
747 ) -> Result<ExtensionResult> {
748 let extension_name = self
749 .with_inner(|inner| inner.commands.get(command_id).map(|(name, _)| name.clone()))
750 .ok_or_else(|| crate::core::EditorError::CommandFailed {
751 message: format!("Command '{command_id}' not found"),
752 })?;
753
754 self.with_inner_mut(|inner| {
755 if let Some(extension) = inner.extensions.get_mut(&extension_name) {
756 extension.execute_command(command_id, args, context)
757 } else {
758 Err(crate::core::EditorError::CommandFailed {
759 message: format!("Extension '{extension_name}' not found"),
760 })
761 }
762 })
763 }
764
765 pub fn list_extensions(&self) -> Vec<String> {
767 self.with_inner(|inner| inner.extensions.keys().cloned().collect())
768 }
769
770 pub fn get_extension_state(&self, extension_name: &str) -> Option<ExtensionState> {
772 self.with_inner(|inner| inner.extension_states.get(extension_name).copied())
773 }
774
775 pub fn list_commands(&self) -> Vec<String> {
777 self.with_inner(|inner| inner.commands.keys().cloned().collect())
778 }
779
780 pub fn get_config(&self, key: &str) -> Option<String> {
782 self.with_inner(|inner| inner.config.get(key).cloned())
783 }
784
785 pub fn set_config(&mut self, key: String, value: String) {
787 self.with_inner_mut(|inner| {
788 inner.config.insert(key, value);
789 });
790 }
791
792 pub fn get_extension_data(&self, extension_name: &str, key: &str) -> Option<String> {
794 self.with_inner(|inner| {
795 inner
796 .extension_data
797 .get(extension_name)
798 .and_then(|data| data.get(key))
799 .cloned()
800 })
801 }
802
803 pub fn set_extension_data(&mut self, extension_name: String, key: String, value: String) {
805 self.with_inner_mut(|inner| {
806 inner
807 .extension_data
808 .entry(extension_name)
809 .or_default()
810 .insert(key, value);
811 });
812 }
813}
814
815impl Default for ExtensionManager {
816 fn default() -> Self {
817 Self::new()
818 }
819}
820
821pub trait MessageHandler: Send + Sync {
823 fn show(&mut self, message: &str, level: MessageLevel) -> Result<()>;
825}
826
827#[cfg(feature = "std")]
829pub struct StdMessageHandler;
830
831#[cfg(feature = "std")]
832impl MessageHandler for StdMessageHandler {
833 fn show(&mut self, message: &str, level: MessageLevel) -> Result<()> {
834 match level {
835 MessageLevel::Error => eprintln!("[ERROR] {message}"),
836 MessageLevel::Warning => eprintln!("[WARN] {message}"),
837 MessageLevel::Info => println!("[INFO] {message}"),
838 MessageLevel::Success => println!("[SUCCESS] {message}"),
839 }
840 Ok(())
841 }
842}
843
844#[cfg(not(feature = "std"))]
846pub struct NoOpMessageHandler;
847
848#[cfg(not(feature = "std"))]
849impl MessageHandler for NoOpMessageHandler {
850 fn show(&mut self, _message: &str, _level: MessageLevel) -> Result<()> {
851 Ok(())
852 }
853}
854
855#[cfg(feature = "std")]
857use std::sync::mpsc::{self, Sender};
858
859#[cfg(feature = "std")]
860pub type EventSender = Sender<DocumentEvent>;
861
862#[cfg(not(feature = "std"))]
863pub type EventSender = (); pub struct EditorContext<'a> {
868 pub document: Option<&'a mut EditorDocument>,
870 #[cfg(feature = "multi-thread")]
872 pub manager: ExtensionManager,
873 #[cfg(not(feature = "multi-thread"))]
875 pub manager: &'a mut ExtensionManager,
876 #[cfg(not(feature = "multi-thread"))]
878 pub manager_mut_state: alloc::rc::Rc<core::cell::RefCell<HashMap<String, String>>>,
879 pub extension_name: String,
881}
882
883impl<'a> ExtensionContext for EditorContext<'a> {
884 fn current_document(&self) -> Option<&EditorDocument> {
885 self.document.as_deref()
886 }
887
888 fn current_document_mut(&mut self) -> Option<&mut EditorDocument> {
889 self.document.as_deref_mut()
890 }
891
892 fn send_event(&mut self, _event: DocumentEvent) -> Result<()> {
893 #[cfg(feature = "std")]
896 {
897 eprintln!("Extension {} sent event: {:?}", self.extension_name, _event);
898 }
899 Ok(())
900 }
901
902 fn get_config(&self, key: &str) -> Option<String> {
903 #[cfg(feature = "multi-thread")]
904 {
905 self.manager.get_config(key)
906 }
907 #[cfg(not(feature = "multi-thread"))]
908 {
909 self.manager.get_config(key)
910 }
911 }
912
913 fn set_config(&mut self, key: String, value: String) -> Result<()> {
914 #[cfg(feature = "multi-thread")]
915 {
916 self.manager.set_config(key.clone(), value.clone());
917 }
918 #[cfg(not(feature = "multi-thread"))]
919 {
920 self.manager_mut_state.borrow_mut().insert(key, value);
922 }
923 Ok(())
924 }
925
926 fn register_command(&mut self, _command: ExtensionCommand) -> Result<()> {
927 #[cfg(feature = "std")]
930 {
931 eprintln!(
932 "Extension {} registered command: {}",
933 self.extension_name, _command.id
934 );
935 }
936 Ok(())
937 }
938
939 fn show_message(&mut self, _message: &str, _level: MessageLevel) -> Result<()> {
940 #[cfg(feature = "std")]
942 {
943 match _level {
944 MessageLevel::Info => eprintln!("[INFO] {}: {}", self.extension_name, _message),
945 MessageLevel::Warning => eprintln!("[WARN] {}: {}", self.extension_name, _message),
946 MessageLevel::Error => eprintln!("[ERROR] {}: {}", self.extension_name, _message),
947 MessageLevel::Success => {
948 eprintln!("[SUCCESS] {}: {}", self.extension_name, _message)
949 }
950 }
951 }
952 Ok(())
953 }
954
955 fn get_extension_data(&self, extension_name: &str, key: &str) -> Option<String> {
956 self.manager.get_extension_data(extension_name, key)
957 }
958
959 fn set_extension_data(&mut self, key: String, value: String) -> Result<()> {
960 self.manager
961 .set_extension_data(self.extension_name.clone(), key, value);
962 Ok(())
963 }
964}
965
966#[cfg(test)]
967mod tests {
968 use super::*;
969 #[cfg(not(feature = "std"))]
970 use alloc::{string::ToString, vec};
971
972 #[test]
973 fn extension_info_creation() {
974 let info = ExtensionInfo::new(
975 "test-extension".to_string(),
976 "1.0.0".to_string(),
977 "Test Author".to_string(),
978 "A test extension".to_string(),
979 )
980 .with_capability(ExtensionCapability::TextProcessing)
981 .with_dependency("core-extension".to_string())
982 .with_homepage("https://example.com".to_string())
983 .with_license("MIT".to_string());
984
985 assert_eq!(info.name, "test-extension");
986 assert_eq!(info.version, "1.0.0");
987 assert!(info.has_capability(&ExtensionCapability::TextProcessing));
988 assert_eq!(info.dependencies.len(), 1);
989 assert_eq!(info.homepage, Some("https://example.com".to_string()));
990 assert_eq!(info.license, Some("MIT".to_string()));
991 }
992
993 #[test]
994 fn extension_capability_description() {
995 let capability = ExtensionCapability::TextProcessing;
996 assert_eq!(
997 capability.description(),
998 "Text processing and transformation"
999 );
1000 }
1001
1002 #[test]
1003 fn extension_state_checks() {
1004 let state = ExtensionState::Active;
1005 assert!(state.is_active());
1006 assert!(state.is_usable());
1007 assert!(!state.is_error());
1008
1009 let error_state = ExtensionState::Error;
1010 assert!(!error_state.is_active());
1011 assert!(!error_state.is_usable());
1012 assert!(error_state.is_error());
1013 }
1014
1015 #[test]
1016 fn extension_command_creation() {
1017 let command = ExtensionCommand::new(
1018 "test-command".to_string(),
1019 "Test Command".to_string(),
1020 "A test command".to_string(),
1021 )
1022 .with_shortcut("Ctrl+T".to_string())
1023 .with_category("Testing".to_string())
1024 .requires_document(false);
1025
1026 assert_eq!(command.id, "test-command");
1027 assert_eq!(command.shortcut, Some("Ctrl+T".to_string()));
1028 assert_eq!(command.category, "Testing");
1029 assert!(!command.requires_document);
1030 }
1031
1032 #[test]
1033 fn extension_result_creation() {
1034 let success = ExtensionResult::success_with_message("Success!".to_string())
1035 .with_data("key".to_string(), "value".to_string());
1036
1037 assert!(success.success);
1038 assert_eq!(success.message, Some("Success!".to_string()));
1039 assert_eq!(success.data.get("key"), Some(&"value".to_string()));
1040
1041 let failure = ExtensionResult::failure("Failed!".to_string());
1042 assert!(!failure.success);
1043 assert_eq!(failure.message, Some("Failed!".to_string()));
1044 }
1045
1046 #[test]
1047 fn extension_manager_creation() {
1048 let manager = ExtensionManager::new();
1049 assert_eq!(manager.list_extensions().len(), 0);
1050 assert_eq!(manager.list_commands().len(), 0);
1051 }
1052
1053 #[test]
1054 fn extension_manager_config() {
1055 let mut manager = ExtensionManager::new();
1056
1057 manager.set_config("test_key".to_string(), "test_value".to_string());
1058 assert_eq!(
1059 manager.get_config("test_key"),
1060 Some("test_value".to_string())
1061 );
1062 assert_eq!(manager.get_config("nonexistent"), None);
1063 }
1064
1065 #[test]
1066 fn extension_manager_data() {
1067 let mut manager = ExtensionManager::new();
1068
1069 manager.set_extension_data("ext1".to_string(), "key".to_string(), "value".to_string());
1070 assert_eq!(
1071 manager.get_extension_data("ext1", "key"),
1072 Some("value".to_string())
1073 );
1074 assert_eq!(manager.get_extension_data("ext1", "nonexistent"), None);
1075 assert_eq!(manager.get_extension_data("ext2", "key"), None);
1076 }
1077
1078 struct TestExtension {
1080 info: ExtensionInfo,
1081 state: ExtensionState,
1082 data: HashMap<String, String>,
1083 }
1084
1085 impl TestExtension {
1086 fn new(name: &str) -> Self {
1087 Self {
1088 info: ExtensionInfo::new(
1089 name.to_string(),
1090 "1.0.0".to_string(),
1091 "Test".to_string(),
1092 "Test extension".to_string(),
1093 ),
1094 state: ExtensionState::Uninitialized,
1095 data: HashMap::new(),
1096 }
1097 }
1098 }
1099
1100 impl EditorExtension for TestExtension {
1101 fn info(&self) -> &ExtensionInfo {
1102 &self.info
1103 }
1104
1105 fn initialize(&mut self, _context: &mut dyn ExtensionContext) -> Result<()> {
1106 self.state = ExtensionState::Active;
1107 Ok(())
1108 }
1109
1110 fn shutdown(&mut self, _context: &mut dyn ExtensionContext) -> Result<()> {
1111 self.state = ExtensionState::Shutdown;
1112 Ok(())
1113 }
1114
1115 fn state(&self) -> ExtensionState {
1116 self.state
1117 }
1118
1119 fn execute_command(
1120 &mut self,
1121 command_id: &str,
1122 _args: &HashMap<String, String>,
1123 _context: &mut dyn ExtensionContext,
1124 ) -> Result<ExtensionResult> {
1125 match command_id {
1126 "test-command" => Ok(ExtensionResult::success_with_message(
1127 "Command executed".to_string(),
1128 )),
1129 _ => Ok(ExtensionResult::failure("Unknown command".to_string())),
1130 }
1131 }
1132
1133 fn commands(&self) -> Vec<ExtensionCommand> {
1134 vec![ExtensionCommand::new(
1135 "test-command".to_string(),
1136 "Test Command".to_string(),
1137 "A test command".to_string(),
1138 )]
1139 }
1140
1141 fn get_data(&self, key: &str) -> Option<String> {
1142 self.data.get(key).cloned()
1143 }
1144
1145 fn set_data(&mut self, key: String, value: String) -> Result<()> {
1146 self.data.insert(key, value);
1147 Ok(())
1148 }
1149 }
1150
1151 #[test]
1152 fn extension_manager_lifecycle() {
1153 let mut manager = ExtensionManager::new();
1154 let _doc = EditorDocument::new();
1155
1156 let extension = Box::new(TestExtension::new("test-ext"));
1157 manager.load_extension(extension).unwrap();
1158
1159 assert_eq!(manager.list_extensions().len(), 1);
1160 assert_eq!(
1161 manager.get_extension_state("test-ext"),
1162 Some(ExtensionState::Uninitialized)
1163 );
1164
1165 {
1166 }
1169
1170 let extension_exists = manager.list_extensions().contains(&"test-ext".to_string());
1172 assert!(extension_exists);
1173 }
1174
1175 #[test]
1176 fn editor_context() {
1177 let mut manager = ExtensionManager::new();
1178
1179 manager.set_config("test".to_string(), "value".to_string());
1181 assert_eq!(manager.get_config("test"), Some("value".to_string()));
1182
1183 manager.set_extension_data("default".to_string(), "key".to_string(), "data".to_string());
1185 assert_eq!(
1186 manager.get_extension_data("default", "key"),
1187 Some("data".to_string())
1188 );
1189 }
1190}