1use crate::model::piece_tree::PieceTree;
2use crate::view::overlay::{OverlayHandle, OverlayNamespace};
3pub use fresh_core::{BufferId, CursorId, SplitDirection, SplitId};
4use serde::{Deserialize, Serialize};
5use std::ops::Range;
6use std::sync::Arc;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub enum Event {
11 Insert {
13 position: usize,
14 text: String,
15 cursor_id: CursorId,
16 },
17
18 Delete {
20 range: Range<usize>,
21 deleted_text: String,
22 cursor_id: CursorId,
23 },
24
25 MoveCursor {
27 cursor_id: CursorId,
28 old_position: usize,
29 new_position: usize,
30 old_anchor: Option<usize>,
31 new_anchor: Option<usize>,
32 old_sticky_column: usize,
33 new_sticky_column: usize,
34 },
35
36 AddCursor {
38 cursor_id: CursorId,
39 position: usize,
40 anchor: Option<usize>,
41 },
42
43 RemoveCursor {
45 cursor_id: CursorId,
46 position: usize,
47 anchor: Option<usize>,
48 },
49
50 Scroll {
52 line_offset: isize,
53 },
54
55 SetViewport {
57 top_line: usize,
58 },
59
60 Recenter,
62
63 SetAnchor {
65 cursor_id: CursorId,
66 position: usize,
67 },
68
69 ClearAnchor {
72 cursor_id: CursorId,
73 },
74
75 ChangeMode {
77 mode: String,
78 },
79
80 AddOverlay {
82 namespace: Option<OverlayNamespace>,
83 range: Range<usize>,
84 face: OverlayFace,
85 priority: i32,
86 message: Option<String>,
87 extend_to_line_end: bool,
89 },
90
91 RemoveOverlay {
93 handle: OverlayHandle,
94 },
95
96 RemoveOverlaysInRange {
98 range: Range<usize>,
99 },
100
101 ClearNamespace {
103 namespace: OverlayNamespace,
104 },
105
106 ClearOverlays,
108
109 ShowPopup {
111 popup: PopupData,
112 },
113
114 HidePopup,
116
117 ClearPopups,
119
120 PopupSelectNext,
122 PopupSelectPrev,
123 PopupPageDown,
124 PopupPageUp,
125
126 AddMarginAnnotation {
129 line: usize,
130 position: MarginPositionData,
131 content: MarginContentData,
132 annotation_id: Option<String>,
133 },
134
135 RemoveMarginAnnotation {
137 annotation_id: String,
138 },
139
140 RemoveMarginAnnotationsAtLine {
142 line: usize,
143 position: MarginPositionData,
144 },
145
146 ClearMarginPosition {
148 position: MarginPositionData,
149 },
150
151 ClearMargins,
153
154 SetLineNumbers {
156 enabled: bool,
157 },
158
159 SplitPane {
162 direction: SplitDirection,
163 new_buffer_id: BufferId,
164 ratio: f32,
165 },
166
167 CloseSplit {
169 split_id: SplitId,
170 },
171
172 SetActiveSplit {
174 split_id: SplitId,
175 },
176
177 AdjustSplitRatio {
179 split_id: SplitId,
180 delta: f32,
181 },
182
183 NextSplit,
185
186 PrevSplit,
188
189 Batch {
192 events: Vec<Event>,
193 description: String,
194 },
195
196 BulkEdit {
203 #[serde(skip)]
205 old_tree: Option<Arc<PieceTree>>,
206 #[serde(skip)]
208 new_tree: Option<Arc<PieceTree>>,
209 old_cursors: Vec<(CursorId, usize, Option<usize>)>,
211 new_cursors: Vec<(CursorId, usize, Option<usize>)>,
213 description: String,
215 },
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
220pub enum OverlayFace {
221 Underline {
222 color: (u8, u8, u8), style: UnderlineStyle,
224 },
225 Background {
226 color: (u8, u8, u8),
227 },
228 Foreground {
229 color: (u8, u8, u8),
230 },
231 Style {
233 color: (u8, u8, u8),
234 bg_color: Option<(u8, u8, u8)>,
235 bold: bool,
236 italic: bool,
237 underline: bool,
238 },
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
243pub enum UnderlineStyle {
244 Straight,
245 Wavy,
246 Dotted,
247 Dashed,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct PopupData {
253 pub title: Option<String>,
254 #[serde(default)]
256 pub description: Option<String>,
257 #[serde(default)]
258 pub transient: bool,
259 pub content: PopupContentData,
260 pub position: PopupPositionData,
261 pub width: u16,
262 pub max_height: u16,
263 pub bordered: bool,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268pub enum PopupContentData {
269 Text(Vec<String>),
270 List {
271 items: Vec<PopupListItemData>,
272 selected: usize,
273 },
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct PopupListItemData {
279 pub text: String,
280 pub detail: Option<String>,
281 pub icon: Option<String>,
282 pub data: Option<String>,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub enum PopupPositionData {
288 AtCursor,
289 BelowCursor,
290 AboveCursor,
291 Fixed { x: u16, y: u16 },
292 Centered,
293 BottomRight,
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
298pub enum MarginPositionData {
299 Left,
300 Right,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
305pub enum MarginContentData {
306 Text(String),
307 Symbol {
308 text: String,
309 color: Option<(u8, u8, u8)>, },
311 Empty,
312}
313
314impl Event {
315 pub fn inverse(&self) -> Option<Self> {
318 match self {
319 Self::Insert { position, text, .. } => {
320 let range = *position..(position + text.len());
321 Some(Self::Delete {
322 range,
323 deleted_text: text.clone(),
324 cursor_id: CursorId::UNDO_SENTINEL,
325 })
326 }
327 Self::Delete {
328 range,
329 deleted_text,
330 ..
331 } => Some(Self::Insert {
332 position: range.start,
333 text: deleted_text.clone(),
334 cursor_id: CursorId::UNDO_SENTINEL,
335 }),
336 Self::Batch {
337 events,
338 description,
339 } => {
340 let inverted: Option<Vec<Self>> =
342 events.iter().rev().map(|e| e.inverse()).collect();
343
344 inverted.map(|inverted_events| Self::Batch {
345 events: inverted_events,
346 description: format!("Undo: {}", description),
347 })
348 }
349 Self::AddCursor {
350 cursor_id,
351 position,
352 anchor,
353 } => {
354 Some(Self::RemoveCursor {
356 cursor_id: *cursor_id,
357 position: *position,
358 anchor: *anchor,
359 })
360 }
361 Self::RemoveCursor {
362 cursor_id,
363 position,
364 anchor,
365 } => {
366 Some(Self::AddCursor {
368 cursor_id: *cursor_id,
369 position: *position,
370 anchor: *anchor,
371 })
372 }
373 Self::MoveCursor {
374 cursor_id,
375 old_position,
376 new_position,
377 old_anchor,
378 new_anchor,
379 old_sticky_column,
380 new_sticky_column,
381 } => {
382 Some(Self::MoveCursor {
384 cursor_id: *cursor_id,
385 old_position: *new_position,
386 new_position: *old_position,
387 old_anchor: *new_anchor,
388 new_anchor: *old_anchor,
389 old_sticky_column: *new_sticky_column,
390 new_sticky_column: *old_sticky_column,
391 })
392 }
393 Self::AddOverlay { .. } => {
394 None
396 }
397 Self::RemoveOverlay { .. } => {
398 None
400 }
401 Self::ClearNamespace { .. } => {
402 None
404 }
405 Self::Scroll { line_offset } => Some(Self::Scroll {
406 line_offset: -line_offset,
407 }),
408 Self::SetViewport { top_line: _ } => {
409 None
411 }
412 Self::ChangeMode { mode: _ } => {
413 None
415 }
416 Self::BulkEdit {
417 old_tree,
418 new_tree,
419 old_cursors,
420 new_cursors,
421 description,
422 } => {
423 Some(Self::BulkEdit {
426 old_tree: new_tree.clone(),
427 new_tree: old_tree.clone(),
428 old_cursors: new_cursors.clone(),
429 new_cursors: old_cursors.clone(),
430 description: format!("Undo: {}", description),
431 })
432 }
433 _ => None,
435 }
436 }
437
438 pub fn modifies_buffer(&self) -> bool {
440 match self {
441 Self::Insert { .. } | Self::Delete { .. } | Self::BulkEdit { .. } => true,
442 Self::Batch { events, .. } => events.iter().any(|e| e.modifies_buffer()),
443 _ => false,
444 }
445 }
446
447 pub fn is_write_action(&self) -> bool {
460 match self {
461 Self::Insert { .. } | Self::Delete { .. } | Self::BulkEdit { .. } => true,
463
464 Self::AddCursor { .. } | Self::RemoveCursor { .. } => true,
466
467 Self::Batch { events, .. } => events.iter().any(|e| e.is_write_action()),
469
470 _ => false,
472 }
473 }
474
475 pub fn cursor_id(&self) -> Option<CursorId> {
477 match self {
478 Self::Insert { cursor_id, .. }
479 | Self::Delete { cursor_id, .. }
480 | Self::MoveCursor { cursor_id, .. }
481 | Self::AddCursor { cursor_id, .. }
482 | Self::RemoveCursor { cursor_id, .. } => Some(*cursor_id),
483 _ => None,
484 }
485 }
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct LogEntry {
491 pub event: Event,
493
494 pub timestamp: u64,
496
497 pub description: Option<String>,
499}
500
501impl LogEntry {
502 pub fn new(event: Event) -> Self {
503 Self {
504 event,
505 timestamp: std::time::SystemTime::now()
506 .duration_since(std::time::UNIX_EPOCH)
507 .unwrap()
508 .as_millis() as u64,
509 description: None,
510 }
511 }
512
513 pub fn with_description(mut self, description: String) -> Self {
514 self.description = Some(description);
515 self
516 }
517}
518
519#[derive(Debug, Clone)]
521pub struct Snapshot {
522 pub log_index: usize,
524
525 pub buffer_state: (),
528
529 pub cursor_positions: Vec<(CursorId, usize, Option<usize>)>,
531}
532
533pub struct EventLog {
535 entries: Vec<LogEntry>,
537
538 current_index: usize,
540
541 snapshots: Vec<Snapshot>,
543
544 snapshot_interval: usize,
546
547 stream_file: Option<std::fs::File>,
549
550 saved_at_index: Option<usize>,
553}
554
555impl EventLog {
556 pub fn new() -> Self {
558 Self {
559 entries: Vec::new(),
560 current_index: 0,
561 snapshots: Vec::new(),
562 snapshot_interval: 100,
563 stream_file: None,
564 saved_at_index: Some(0), }
566 }
567
568 pub fn mark_saved(&mut self) {
571 self.saved_at_index = Some(self.current_index);
572 }
573
574 pub fn is_at_saved_position(&self) -> bool {
578 match self.saved_at_index {
579 None => false,
580 Some(saved_idx) if saved_idx == self.current_index => true,
581 Some(saved_idx) => {
582 let (start, end) = if saved_idx < self.current_index {
585 (saved_idx, self.current_index)
586 } else {
587 (self.current_index, saved_idx)
588 };
589
590 self.entries[start..end]
592 .iter()
593 .all(|entry| !entry.event.modifies_buffer())
594 }
595 }
596 }
597
598 pub fn enable_streaming<P: AsRef<std::path::Path>>(&mut self, path: P) -> std::io::Result<()> {
600 use std::io::Write;
601
602 let mut file = std::fs::OpenOptions::new()
603 .create(true)
604 .write(true)
605 .truncate(true)
606 .open(path)?;
607
608 writeln!(file, "# Event Log Stream")?;
610 writeln!(file, "# Started at: {}", chrono::Local::now())?;
611 writeln!(file, "# Format: JSON Lines (one event per line)")?;
612 writeln!(file, "#")?;
613
614 self.stream_file = Some(file);
615 Ok(())
616 }
617
618 pub fn disable_streaming(&mut self) {
620 self.stream_file = None;
621 }
622
623 pub fn log_render_state(
625 &mut self,
626 cursor_pos: usize,
627 screen_cursor_x: u16,
628 screen_cursor_y: u16,
629 buffer_len: usize,
630 ) {
631 if let Some(ref mut file) = self.stream_file {
632 use std::io::Write;
633
634 let render_info = serde_json::json!({
635 "type": "render",
636 "timestamp": chrono::Local::now().to_rfc3339(),
637 "cursor_position": cursor_pos,
638 "screen_cursor": {"x": screen_cursor_x, "y": screen_cursor_y},
639 "buffer_length": buffer_len,
640 });
641
642 if let Err(e) = writeln!(file, "{render_info}") {
643 tracing::trace!("Warning: Failed to write render info to stream: {e}");
644 }
645 if let Err(e) = file.flush() {
646 tracing::trace!("Warning: Failed to flush event stream: {e}");
647 }
648 }
649 }
650
651 pub fn log_keystroke(&mut self, key_code: &str, modifiers: &str) {
653 if let Some(ref mut file) = self.stream_file {
654 use std::io::Write;
655
656 let keystroke_info = serde_json::json!({
657 "type": "keystroke",
658 "timestamp": chrono::Local::now().to_rfc3339(),
659 "key": key_code,
660 "modifiers": modifiers,
661 });
662
663 if let Err(e) = writeln!(file, "{keystroke_info}") {
664 tracing::trace!("Warning: Failed to write keystroke to stream: {e}");
665 }
666 if let Err(e) = file.flush() {
667 tracing::trace!("Warning: Failed to flush event stream: {e}");
668 }
669 }
670 }
671
672 pub fn append(&mut self, event: Event) -> usize {
674 if self.current_index < self.entries.len() {
676 self.entries.truncate(self.current_index);
677
678 if let Some(saved_idx) = self.saved_at_index {
680 if saved_idx > self.current_index {
681 self.saved_at_index = None;
682 }
683 }
684 }
685
686 if let Some(ref mut file) = self.stream_file {
688 use std::io::Write;
689
690 let stream_entry = serde_json::json!({
691 "index": self.entries.len(),
692 "timestamp": chrono::Local::now().to_rfc3339(),
693 "event": event,
694 });
695
696 if let Err(e) = writeln!(file, "{stream_entry}") {
698 tracing::trace!("Warning: Failed to write to event stream: {e}");
699 }
700 if let Err(e) = file.flush() {
701 tracing::trace!("Warning: Failed to flush event stream: {e}");
702 }
703 }
704
705 let entry = LogEntry::new(event);
706 self.entries.push(entry);
707 self.current_index = self.entries.len();
708
709 if self.entries.len().is_multiple_of(self.snapshot_interval) {
711 }
714
715 self.current_index - 1
716 }
717
718 pub fn current_index(&self) -> usize {
720 self.current_index
721 }
722
723 pub fn len(&self) -> usize {
725 self.entries.len()
726 }
727
728 pub fn is_empty(&self) -> bool {
730 self.entries.is_empty()
731 }
732
733 pub fn can_undo(&self) -> bool {
735 self.current_index > 0
736 }
737
738 pub fn can_redo(&self) -> bool {
740 self.current_index < self.entries.len()
741 }
742
743 pub fn undo(&mut self) -> Vec<Event> {
747 let mut inverse_events = Vec::new();
748 let mut found_write_action = false;
749
750 while self.can_undo() && !found_write_action {
752 self.current_index -= 1;
753 let event = &self.entries[self.current_index].event;
754
755 if event.is_write_action() {
757 found_write_action = true;
758 }
759
760 if let Some(inverse) = event.inverse() {
762 inverse_events.push(inverse);
763 }
764 }
766
767 inverse_events
768 }
769
770 pub fn redo(&mut self) -> Vec<Event> {
774 let mut events = Vec::new();
775 let mut found_write_action = false;
776
777 while self.can_redo() {
779 let event = self.entries[self.current_index].event.clone();
780
781 if found_write_action && event.is_write_action() {
783 break;
785 }
786
787 self.current_index += 1;
788
789 if event.is_write_action() {
791 found_write_action = true;
792 }
793
794 events.push(event);
795 }
796
797 events
798 }
799
800 pub fn entries(&self) -> &[LogEntry] {
802 &self.entries
803 }
804
805 pub fn range(&self, range: Range<usize>) -> &[LogEntry] {
807 &self.entries[range]
808 }
809
810 pub fn last_event(&self) -> Option<&Event> {
812 if self.current_index > 0 {
813 Some(&self.entries[self.current_index - 1].event)
814 } else {
815 None
816 }
817 }
818
819 pub fn clear(&mut self) {
821 self.entries.clear();
822 self.current_index = 0;
823 self.snapshots.clear();
824 }
825
826 pub fn save_to_file(&self, path: &std::path::Path) -> std::io::Result<()> {
828 use std::io::Write;
829 let file = std::fs::File::create(path)?;
830 let mut writer = std::io::BufWriter::new(file);
831
832 for entry in &self.entries {
833 let json = serde_json::to_string(entry)?;
834 writeln!(writer, "{json}")?;
835 }
836
837 Ok(())
838 }
839
840 pub fn load_from_file(path: &std::path::Path) -> std::io::Result<Self> {
842 use std::io::BufRead;
843 let file = std::fs::File::open(path)?;
844 let reader = std::io::BufReader::new(file);
845
846 let mut log = Self::new();
847
848 for line in reader.lines() {
849 let line = line?;
850 if line.trim().is_empty() {
851 continue;
852 }
853 let entry: LogEntry = serde_json::from_str(&line)?;
854 log.entries.push(entry);
855 }
856
857 log.current_index = log.entries.len();
858
859 Ok(log)
860 }
861
862 pub fn set_snapshot_interval(&mut self, interval: usize) {
864 self.snapshot_interval = interval;
865 }
866}
867
868impl Default for EventLog {
869 fn default() -> Self {
870 Self::new()
871 }
872}
873
874#[cfg(test)]
875mod tests {
876 use super::*;
877
878 #[cfg(test)]
880 mod property_tests {
881 use super::*;
882 use proptest::prelude::*;
883
884 fn arb_event() -> impl Strategy<Value = Event> {
886 prop_oneof![
887 (0usize..1000, ".{1,50}").prop_map(|(pos, text)| Event::Insert {
889 position: pos,
890 text,
891 cursor_id: CursorId(0),
892 }),
893 (0usize..1000, 1usize..50).prop_map(|(pos, len)| Event::Delete {
895 range: pos..pos + len,
896 deleted_text: "x".repeat(len),
897 cursor_id: CursorId(0),
898 }),
899 ]
900 }
901
902 proptest! {
903 #[test]
905 fn event_inverse_property(event in arb_event()) {
906 if let Some(inverse) = event.inverse() {
907 if let Some(double_inverse) = inverse.inverse() {
910 match (&event, &double_inverse) {
911 (Event::Insert { position: p1, text: t1, .. },
912 Event::Insert { position: p2, text: t2, .. }) => {
913 assert_eq!(p1, p2);
914 assert_eq!(t1, t2);
915 }
916 (Event::Delete { range: r1, deleted_text: dt1, .. },
917 Event::Delete { range: r2, deleted_text: dt2, .. }) => {
918 assert_eq!(r1, r2);
919 assert_eq!(dt1, dt2);
920 }
921 _ => {}
922 }
923 }
924 }
925 }
926
927 #[test]
929 fn undo_redo_inverse(events in prop::collection::vec(arb_event(), 1..20)) {
930 let mut log = EventLog::new();
931
932 for event in &events {
934 log.append(event.clone());
935 }
936
937 let after_append = log.current_index();
938
939 let mut undo_count = 0;
941 while log.can_undo() {
942 log.undo();
943 undo_count += 1;
944 }
945
946 assert_eq!(log.current_index(), 0);
947 assert_eq!(undo_count, events.len());
948
949 let mut redo_count = 0;
951 while log.can_redo() {
952 log.redo();
953 redo_count += 1;
954 }
955
956 assert_eq!(log.current_index(), after_append);
957 assert_eq!(redo_count, events.len());
958 }
959
960 #[test]
962 fn append_after_undo_truncates(
963 initial_events in prop::collection::vec(arb_event(), 2..10),
964 new_event in arb_event()
965 ) {
966 let mut log = EventLog::new();
967
968 for event in &initial_events {
969 log.append(event.clone());
970 }
971
972 log.undo();
974 let index_after_undo = log.current_index();
975
976 log.append(new_event);
978
979 assert_eq!(log.current_index(), index_after_undo + 1);
981 assert!(!log.can_redo());
982 }
983 }
984 }
985
986 #[test]
987 fn test_event_log_append() {
988 let mut log = EventLog::new();
989 let event = Event::Insert {
990 position: 0,
991 text: "hello".to_string(),
992 cursor_id: CursorId(0),
993 };
994
995 let index = log.append(event);
996 assert_eq!(index, 0);
997 assert_eq!(log.current_index(), 1);
998 assert_eq!(log.entries().len(), 1);
999 }
1000
1001 #[test]
1002 fn test_undo_redo() {
1003 let mut log = EventLog::new();
1004
1005 log.append(Event::Insert {
1006 position: 0,
1007 text: "a".to_string(),
1008 cursor_id: CursorId(0),
1009 });
1010
1011 log.append(Event::Insert {
1012 position: 1,
1013 text: "b".to_string(),
1014 cursor_id: CursorId(0),
1015 });
1016
1017 assert_eq!(log.current_index(), 2);
1018 assert!(log.can_undo());
1019 assert!(!log.can_redo());
1020
1021 log.undo();
1022 assert_eq!(log.current_index(), 1);
1023 assert!(log.can_undo());
1024 assert!(log.can_redo());
1025
1026 log.undo();
1027 assert_eq!(log.current_index(), 0);
1028 assert!(!log.can_undo());
1029 assert!(log.can_redo());
1030
1031 log.redo();
1032 assert_eq!(log.current_index(), 1);
1033 }
1034
1035 #[test]
1036 fn test_event_inverse() {
1037 let insert = Event::Insert {
1038 position: 5,
1039 text: "hello".to_string(),
1040 cursor_id: CursorId(0),
1041 };
1042
1043 let inverse = insert.inverse().unwrap();
1044 match inverse {
1045 Event::Delete {
1046 range,
1047 deleted_text,
1048 ..
1049 } => {
1050 assert_eq!(range, 5..10);
1051 assert_eq!(deleted_text, "hello");
1052 }
1053 _ => panic!("Expected Delete event"),
1054 }
1055 }
1056
1057 #[test]
1058 fn test_truncate_on_new_event_after_undo() {
1059 let mut log = EventLog::new();
1060
1061 log.append(Event::Insert {
1062 position: 0,
1063 text: "a".to_string(),
1064 cursor_id: CursorId(0),
1065 });
1066
1067 log.append(Event::Insert {
1068 position: 1,
1069 text: "b".to_string(),
1070 cursor_id: CursorId(0),
1071 });
1072
1073 log.undo();
1074 assert_eq!(log.entries().len(), 2);
1075
1076 log.append(Event::Insert {
1078 position: 1,
1079 text: "c".to_string(),
1080 cursor_id: CursorId(0),
1081 });
1082
1083 assert_eq!(log.entries().len(), 2);
1084 assert_eq!(log.current_index(), 2);
1085 }
1086
1087 #[test]
1096 fn test_is_at_saved_position_after_truncate() {
1097 let mut log = EventLog::new();
1098
1099 for i in 0..150 {
1101 log.append(Event::Insert {
1102 position: i,
1103 text: "x".to_string(),
1104 cursor_id: CursorId(0),
1105 });
1106 }
1107
1108 assert_eq!(log.entries().len(), 150);
1109 assert_eq!(log.current_index(), 150);
1110
1111 log.mark_saved();
1113
1114 for _ in 0..30 {
1116 log.undo();
1117 }
1118 assert_eq!(log.current_index(), 120);
1119 assert_eq!(log.entries().len(), 150);
1120
1121 log.append(Event::Insert {
1123 position: 0,
1124 text: "NEW".to_string(),
1125 cursor_id: CursorId(0),
1126 });
1127
1128 assert_eq!(log.entries().len(), 121);
1130 assert_eq!(log.current_index(), 121);
1131
1132 let result = log.is_at_saved_position();
1136
1137 assert!(
1139 !result,
1140 "Should not be at saved position after undo + new edit"
1141 );
1142 }
1143}