1use {
41 reovim_driver_clipboard::{ClipboardKey, ClipboardProviderRegistry},
42 reovim_driver_command_types::{CommandContext, CommandResult, RuntimeSignal},
43 reovim_driver_layout::{
44 LayerId, NavigateDirection, OverlayConstraints, Rect, SplitDirection, WindowPlacement,
45 },
46 reovim_driver_undo::{UndoKey, UndoProviderRegistry},
47 reovim_kernel::api::v1::{
48 BufferId, CommandId, Edit, KernelContext, ModeId, OptionValue, Position, TabId, UndoResult,
49 WindowId,
50 events::kernel::{
51 CursorMoved, LayoutChangeKind, LayoutChanged, SplitDirection as KernelSplitDirection,
52 },
53 },
54};
55
56use crate::{
57 Selection, Session, SessionExtension, Window,
58 api::{
59 BufferApi, BufferError, ChangeTracker, ClipboardApi, CommandApi, CommandExecutor,
60 CompositorApi, CompositorError, ExtensionApi, ModeApi, ModeError, RegisterApi,
61 RegisterContent, StateChanges, UndoApi, WindowApi, WindowError,
62 },
63 transition::{PopResult, TransitionContext},
64};
65
66pub struct SessionRuntime<'a> {
98 owner: Option<crate::ClientId>,
103 session: &'a mut Session,
107 mode_stack: &'a mut reovim_kernel::api::v1::ModeStack,
112 windows: &'a mut crate::WindowLayout,
117 extensions: &'a mut crate::ExtensionMap,
122 shared_extensions: Option<&'a mut crate::ExtensionMap>,
128 compositor: &'a mut Option<Box<dyn reovim_driver_layout::RootCompositor>>,
134 tabs: &'a mut crate::TabPageSet,
136 registers: &'a mut reovim_kernel::api::v1::RegisterBank,
141 clipboard_history: &'a mut reovim_kernel::api::v1::HistoryRing,
145 local_marks: &'a mut reovim_kernel::api::v1::MarkBank,
149 jumplist: &'a mut reovim_kernel::api::v1::Jumplist,
151 active_buffer: &'a mut Option<reovim_kernel::api::v1::BufferId>,
155 kernel: &'a KernelContext,
157 executor: &'a dyn CommandExecutor,
159 screen: Rect,
161 changes: StateChanges,
163 signals: Vec<RuntimeSignal>,
166 command_depth: usize,
171 cursor_snapshot: Option<(u32, u32)>,
176}
177
178impl<'a> SessionRuntime<'a> {
179 #[allow(clippy::needless_pass_by_value)] pub fn new(
222 session: &'a mut Session,
223 client: crate::ClientContext<'a>,
224 kernel: &'a KernelContext,
225 executor: &'a dyn CommandExecutor,
226 ) -> Self {
227 let screen = {
228 let (width, height) = *client.terminal_size;
229 Rect::new(0, 0, width, height)
230 };
231 #[allow(clippy::cast_possible_truncation)]
234 let cursor_snapshot = client
235 .windows
236 .active()
237 .map(|w| (w.cursor.line as u32, w.cursor.column as u32));
238 Self {
239 owner: None,
240 session,
241 mode_stack: client.mode_stack,
242 windows: client.windows,
243 extensions: client.extensions,
244 shared_extensions: None,
245 compositor: client.compositor,
246 tabs: client.tabs,
247 registers: client.registers,
248 clipboard_history: client.clipboard_history,
249 local_marks: client.local_marks,
250 jumplist: client.jumplist,
251 active_buffer: client.active_buffer,
252 kernel,
253 executor,
254 screen,
255 changes: StateChanges::new(),
256 signals: Vec::new(),
257 command_depth: 0,
258 cursor_snapshot,
259 }
260 }
261
262 #[allow(clippy::needless_pass_by_value)] pub fn with_owner(
295 owner: crate::ClientId,
296 session: &'a mut Session,
297 client: crate::ClientContext<'a>,
298 kernel: &'a KernelContext,
299 executor: &'a dyn CommandExecutor,
300 ) -> Self {
301 let screen = {
302 let (width, height) = *client.terminal_size;
303 Rect::new(0, 0, width, height)
304 };
305 #[allow(clippy::cast_possible_truncation)]
306 let cursor_snapshot = client
307 .windows
308 .active()
309 .map(|w| (w.cursor.line as u32, w.cursor.column as u32));
310 Self {
311 owner: Some(owner),
312 session,
313 mode_stack: client.mode_stack,
314 windows: client.windows,
315 extensions: client.extensions,
316 shared_extensions: None,
317 compositor: client.compositor,
318 tabs: client.tabs,
319 registers: client.registers,
320 clipboard_history: client.clipboard_history,
321 local_marks: client.local_marks,
322 jumplist: client.jumplist,
323 active_buffer: client.active_buffer,
324 kernel,
325 executor,
326 screen,
327 changes: StateChanges::new(),
328 signals: Vec::new(),
329 command_depth: 0,
330 cursor_snapshot,
331 }
332 }
333
334 #[must_use]
352 pub const fn owner(&self) -> Option<crate::ClientId> {
353 self.owner
354 }
355
356 #[must_use]
361 pub const fn with_shared_extensions(mut self, extensions: &'a mut crate::ExtensionMap) -> Self {
362 self.shared_extensions = Some(extensions);
363 self
364 }
365
366 pub fn signal(&mut self, signal: RuntimeSignal) {
373 self.signals.push(signal);
374 }
375
376 pub fn take_signals(&mut self) -> Vec<RuntimeSignal> {
381 std::mem::take(&mut self.signals)
382 }
383
384 #[must_use]
390 pub fn has_compositor(&self) -> bool {
391 self.compositor.is_some()
392 }
393
394 #[must_use]
396 pub const fn session(&self) -> &Session {
397 self.session
398 }
399
400 pub const fn session_mut(&mut self) -> &mut Session {
402 self.session
403 }
404
405 #[must_use]
407 pub const fn kernel(&self) -> &KernelContext {
408 self.kernel
409 }
410
411 pub fn with_buffer_read<F, R>(&self, buffer: BufferId, f: F) -> Option<R>
435 where
436 F: FnOnce(&reovim_kernel::api::v1::Buffer) -> R,
437 {
438 let buf_arc = self.kernel.buffers.get(buffer)?;
439 let buf = buf_arc.read();
440 Some(f(&buf))
441 }
442
443 pub fn record_global_option_change(&mut self, name: impl Into<String>, value: OptionValue) {
450 self.changes.record_global_option_change(name, value);
451 }
452
453 pub fn record_window_option_change(
458 &mut self,
459 name: impl Into<String>,
460 value: OptionValue,
461 window_id: WindowId,
462 ) {
463 self.changes
464 .record_window_option_change(name, value, window_id);
465 }
466
467 pub fn record_buffer_modified(&mut self, buffer: BufferId) {
472 self.changes.record_buffer_modified(buffer);
473 }
474
475 #[must_use]
491 pub const fn windows(&self) -> &crate::WindowLayout {
492 self.windows
493 }
494
495 #[allow(clippy::missing_const_for_fn)] pub fn windows_mut(&mut self) -> &mut crate::WindowLayout {
509 self.windows
510 }
511
512 #[must_use]
514 pub const fn registers(&self) -> &reovim_kernel::api::v1::RegisterBank {
515 self.registers
516 }
517
518 #[allow(clippy::missing_const_for_fn)]
520 pub fn registers_mut(&mut self) -> &mut reovim_kernel::api::v1::RegisterBank {
521 self.registers
522 }
523
524 #[must_use]
526 pub const fn clipboard_history(&self) -> &reovim_kernel::api::v1::HistoryRing {
527 self.clipboard_history
528 }
529
530 #[allow(clippy::missing_const_for_fn)]
532 pub fn clipboard_history_mut(&mut self) -> &mut reovim_kernel::api::v1::HistoryRing {
533 self.clipboard_history
534 }
535
536 pub const fn kernel_and_registers(
542 &mut self,
543 ) -> (
544 &KernelContext,
545 &mut reovim_kernel::api::v1::RegisterBank,
546 &mut reovim_kernel::api::v1::HistoryRing,
547 ) {
548 (self.kernel, self.registers, self.clipboard_history)
549 }
550
551 #[must_use]
553 pub const fn local_marks(&self) -> &reovim_kernel::api::v1::MarkBank {
554 self.local_marks
555 }
556
557 #[allow(clippy::missing_const_for_fn)]
559 pub fn local_marks_mut(&mut self) -> &mut reovim_kernel::api::v1::MarkBank {
560 self.local_marks
561 }
562
563 #[must_use]
565 pub const fn jumplist(&self) -> &reovim_kernel::api::v1::Jumplist {
566 self.jumplist
567 }
568
569 #[allow(clippy::missing_const_for_fn)]
571 pub fn jumplist_mut(&mut self) -> &mut reovim_kernel::api::v1::Jumplist {
572 self.jumplist
573 }
574}
575
576impl ModeApi for SessionRuntime<'_> {
583 fn current_mode(&self) -> &ModeId {
584 self.mode_stack.current()
585 }
586
587 fn home_mode(&self) -> &ModeId {
588 self.mode_stack.home()
589 }
590
591 fn mode_depth(&self) -> usize {
592 self.mode_stack.depth()
593 }
594
595 fn is_mode_active(&self, mode: &ModeId) -> bool {
596 self.mode_stack.contains(mode)
597 }
598
599 fn mode_stack(&self) -> Vec<ModeId> {
600 self.mode_stack.as_slice().to_vec()
601 }
602
603 fn push_mode(&mut self, mode: ModeId, _ctx: TransitionContext) {
604 self.mode_stack.push(mode);
605 self.changes.record_mode_change();
606 }
607
608 fn pop_mode(&mut self, _result: Option<PopResult>) -> Result<(), ModeError> {
609 if self.mode_stack.depth() <= 1 {
610 return Err(ModeError::CannotPopHomeMode);
611 }
612 self.mode_stack.pop();
613 self.changes.record_mode_change();
614 Ok(())
615 }
616
617 fn set_mode(&mut self, mode: ModeId, _ctx: TransitionContext) {
618 self.mode_stack.set(mode);
619 self.changes.record_mode_change();
620 }
621}
622
623impl BufferApi for SessionRuntime<'_> {
626 fn active_buffer(&self) -> Option<BufferId> {
627 *self.active_buffer
628 }
629
630 fn set_active_buffer(&mut self, id: Option<BufferId>) {
631 *self.active_buffer = id;
632 }
633
634 fn buffer_line(&self, buffer: BufferId, line: usize) -> Option<String> {
635 self.kernel
636 .buffers
637 .get(buffer)
638 .and_then(|buf| buf.read().line(line).map(String::from))
639 }
640
641 fn buffer_line_count(&self, buffer: BufferId) -> Option<usize> {
642 self.kernel
643 .buffers
644 .get(buffer)
645 .map(|buf| buf.read().line_count())
646 }
647
648 fn buffer_line_len(&self, buffer: BufferId, line: usize) -> Option<usize> {
649 self.kernel
650 .buffers
651 .get(buffer)
652 .and_then(|buf| buf.read().line_len(line))
653 }
654
655 #[allow(clippy::significant_drop_tightening)]
656 #[cfg_attr(coverage_nightly, coverage(off))]
657 fn buffer_text_range(
658 &self,
659 buffer: BufferId,
660 start: Position,
661 end: Position,
662 ) -> Option<String> {
663 let buf_arc = self.kernel.buffers.get(buffer)?;
664 let buf = buf_arc.read();
665
666 let mut result = String::new();
668
669 if start.line == end.line {
670 if let Some(line) = buf.line(start.line) {
672 let char_len = line.chars().count();
673 let start_col = start.column.min(char_len);
674 let end_col = end.column.min(char_len);
675 if start_col < end_col {
676 let sb = char_col_to_byte(line, start_col);
677 let eb = char_col_to_byte(line, end_col);
678 result.push_str(&line[sb..eb]);
679 }
680 }
681 } else {
682 if let Some(line) = buf.line(start.line) {
685 let char_len = line.chars().count();
686 let start_col = start.column.min(char_len);
687 let sb = char_col_to_byte(line, start_col);
688 result.push_str(&line[sb..]);
689 result.push('\n');
690 }
691
692 for line_idx in (start.line + 1)..end.line {
694 if let Some(line) = buf.line(line_idx) {
695 result.push_str(line);
696 result.push('\n');
697 }
698 }
699
700 if let Some(line) = buf.line(end.line) {
702 let char_len = line.chars().count();
703 let end_col = end.column.min(char_len);
704 let eb = char_col_to_byte(line, end_col);
705 result.push_str(&line[..eb]);
706 }
707 }
708
709 Some(result)
710 }
711
712 fn buffer_content(&self, buffer: BufferId) -> Option<String> {
713 self.kernel
714 .buffers
715 .get(buffer)
716 .map(|buf| buf.read().content())
717 }
718
719 fn buffer_file_path(&self, buffer: BufferId) -> Option<String> {
720 self.kernel
721 .buffers
722 .get(buffer)
723 .and_then(|buf| buf.read().file_path().map(String::from))
724 }
725
726 fn is_buffer_modified(&self, buffer: BufferId) -> Option<bool> {
727 self.kernel
728 .buffers
729 .get(buffer)
730 .map(|buf| buf.read().is_modified())
731 }
732
733 fn set_buffer_modified(&mut self, buffer: BufferId, modified: bool) {
734 if let Some(buf) = self.kernel.buffers.get(buffer) {
735 buf.write().set_modified(modified);
736 }
737 }
738
739 fn insert_text(&mut self, buffer: BufferId, pos: Position, text: &str) {
740 if let Some(buf) = self.kernel.buffers.get(buffer) {
741 let cursor_before = self.windows().active().map_or_else(
744 || Position::new(0, 0),
745 |w| Position::new(w.cursor.line, w.cursor.column),
746 );
747
748 let byte_offset = buf.read().position_to_byte(pos);
750
751 buf.write().insert_at(pos, text);
752
753 let cursor_after = cursor_before;
755
756 let edit = Edit::Insert {
758 position: pos,
759 text: text.to_string(),
760 };
761 self.record_edit_mine(buffer, vec![edit], cursor_before, cursor_after);
762
763 #[allow(clippy::cast_possible_truncation)]
765 {
766 use reovim_kernel::api::v1::events::kernel::{BufferModified, Modification};
767 let modification = Modification::Insert {
768 start: (pos.line as u32, pos.column as u32),
769 text: text.to_string(),
770 start_byte: byte_offset,
771 };
772 self.kernel.event_bus.emit(BufferModified {
773 buffer_id: buffer.as_usize() as u64,
774 modification: modification.clone(),
775 });
776 self.changes
777 .record_buffer_modified_with_edit(buffer, modification);
778 }
779 }
780 }
781
782 fn delete_range(&mut self, buffer: BufferId, start: Position, end: Position) {
783 if let Some(buf) = self.kernel.buffers.get(buffer) {
784 let cursor_before = self.windows().active().map_or_else(
787 || Position::new(0, 0),
788 |w| Position::new(w.cursor.line, w.cursor.column),
789 );
790
791 let byte_offset = buf.read().position_to_byte(start);
793
794 let deleted_text = {
795 let mut b = buf.write();
796 b.delete_range(start, end)
797 };
798
799 let cursor_after = cursor_before;
801
802 if deleted_text.is_empty() {
804 self.changes.record_buffer_modified(buffer);
805 } else {
806 let edit = Edit::Delete {
807 position: start,
808 text: deleted_text.clone(),
809 };
810 self.record_edit_mine(buffer, vec![edit], cursor_before, cursor_after);
811
812 #[allow(clippy::cast_possible_truncation)]
814 {
815 use reovim_kernel::api::v1::events::kernel::{BufferModified, Modification};
816 let modification = Modification::Delete {
817 start: (start.line as u32, start.column as u32),
818 end: (end.line as u32, end.column as u32),
819 text: deleted_text,
820 start_byte: byte_offset,
821 };
822 self.kernel.event_bus.emit(BufferModified {
823 buffer_id: buffer.as_usize() as u64,
824 modification: modification.clone(),
825 });
826 self.changes
827 .record_buffer_modified_with_edit(buffer, modification);
828 }
829 }
830 }
831 }
832
833 #[cfg_attr(coverage_nightly, coverage(off))]
834 fn replace_content(&mut self, buffer: BufferId, content: &str) {
835 if let Some(buf) = self.kernel.buffers.get(buffer) {
836 let cursor_before = self.windows().active().map_or_else(
837 || Position::new(0, 0),
838 |w| Position::new(w.cursor.line, w.cursor.column),
839 );
840
841 let old_content = buf.read().content();
842 buf.write().set_content(content);
843
844 let edits = vec![
846 Edit::Delete {
847 position: Position::new(0, 0),
848 text: old_content,
849 },
850 Edit::Insert {
851 position: Position::new(0, 0),
852 text: content.to_string(),
853 },
854 ];
855 self.record_edit_mine(buffer, edits, cursor_before, cursor_before);
856
857 #[allow(clippy::cast_possible_truncation)]
859 {
860 use reovim_kernel::api::v1::events::kernel::{BufferModified, Modification};
861 let modification = Modification::FullReplace;
862 self.kernel.event_bus.emit(BufferModified {
863 buffer_id: buffer.as_usize() as u64,
864 modification: modification.clone(),
865 });
866 self.changes
867 .record_buffer_modified_with_edit(buffer, modification);
868 }
869 }
870 }
871
872 fn create_buffer(&mut self, name: Option<&str>, content: &str) -> BufferId {
873 use reovim_kernel::api::v1::Buffer;
874
875 let mut buffer = Buffer::from_string(content);
876 if let Some(name) = name {
877 buffer.set_file_path(Some(name.to_string()));
878 }
879 let id = self.kernel.buffers.register(buffer);
880 self.changes.record_buffer_created(id);
881 id
882 }
883
884 fn delete_buffer(&mut self, buffer: BufferId) -> Result<(), BufferError> {
885 if self.kernel.buffers.count() <= 1 {
886 return Err(BufferError::CannotDeleteLastBuffer);
887 }
888 if self.kernel.buffers.unregister(buffer).is_err() {
889 return Err(BufferError::NotFound(buffer));
890 }
891 self.changes.record_buffer_deleted(buffer);
892 Ok(())
893 }
894
895 fn rename_buffer(&mut self, buffer: BufferId, new_name: &str) {
896 if let Some(buf) = self.kernel.buffers.get(buffer) {
897 buf.write().set_file_path(Some(new_name.to_string()));
898 self.changes
899 .record_buffer_renamed(buffer, new_name.to_string());
900 }
901 }
902}
903
904impl WindowApi for SessionRuntime<'_> {
907 fn active_window(&self) -> Option<WindowId> {
908 self.windows.active_id() }
910
911 fn cursor_position(&self) -> Option<Position> {
912 let window = self.windows().active()?;
913 Some(Position::new(window.cursor.line, window.cursor.column))
914 }
915
916 fn window_count(&self) -> usize {
917 self.windows.len() }
919
920 fn window_buffer(&self, window: WindowId) -> Option<BufferId> {
921 self.windows.get(window).and_then(|w| w.buffer_id) }
923
924 fn create_window(&mut self, buffer: Option<BufferId>) -> WindowId {
925 let mut window = Window::new();
926 window.buffer_id = buffer;
927 let id = window.id;
928 self.windows.add(window); self.changes.record_window_created(id);
930 id
931 }
932
933 fn close_window(&mut self, window: WindowId) -> Result<(), WindowError> {
934 if self.windows.len() <= 1 {
935 return Err(WindowError::CannotCloseLastWindow);
937 }
938 if self.windows.remove(window) {
939 self.changes.record_window_closed(window);
940 Ok(())
941 } else {
942 Err(WindowError::NotFound(window))
943 }
944 }
945
946 fn focus_window(&mut self, window: WindowId) -> Result<(), WindowError> {
947 if !self.windows.set_active(window) {
948 return Err(WindowError::NotFound(window));
950 }
951 self.changes.record_focus_change();
952 Ok(())
953 }
954
955 fn set_window_buffer(&mut self, window: WindowId, buffer: BufferId) -> Result<(), WindowError> {
956 if self.kernel.buffers.get(buffer).is_none() {
957 return Err(WindowError::BufferNotFound(buffer));
958 }
959 if let Some(w) = self.windows.get_mut(window) {
960 w.selection = None;
965 w.buffer_id = Some(buffer);
966 self.changes.window_changed = true;
967 Ok(())
968 } else {
969 Err(WindowError::NotFound(window))
970 }
971 }
972
973 fn set_active_selection(&mut self, selection: Option<Selection>) {
974 if let Some(w) = self.windows.active_mut() {
975 w.selection = selection;
976 }
977 }
978
979 fn active_selection(&self) -> Option<&Selection> {
980 self.windows.active().and_then(|w| w.selection.as_ref())
981 }
982}
983
984impl RegisterApi for SessionRuntime<'_> {
987 fn get_register(&self, name: Option<char>) -> Option<RegisterContent> {
988 match name {
989 Some(n) if n.is_ascii_digit() => self.clipboard_history.get_numbered(n),
991
992 _ => self.registers.get_by_name(name).cloned(),
997 }
998 }
999
1000 fn set_register(&mut self, name: Option<char>, content: RegisterContent) {
1001 match name {
1002 Some(n) if n.is_ascii_digit() => {
1004 }
1006
1007 _ => {
1009 self.registers.set_by_name(name, content);
1010 }
1011 }
1012 }
1013}
1014
1015#[cfg_attr(coverage_nightly, coverage(off))]
1018impl ClipboardApi for SessionRuntime<'_> {
1019 fn copy_to_clipboard(&self, text: &str) -> bool {
1020 if let Some(registry) = self.kernel.services.get::<ClipboardProviderRegistry>()
1021 && let Some(provider) = registry.get(&ClipboardKey::Default)
1022 {
1023 provider.copy_to_clipboard(text).is_ok()
1024 } else {
1025 false
1026 }
1027 }
1028
1029 fn paste_from_clipboard(&self) -> Option<String> {
1030 if let Some(registry) = self.kernel.services.get::<ClipboardProviderRegistry>()
1031 && let Some(provider) = registry.get(&ClipboardKey::Default)
1032 {
1033 provider.paste_from_clipboard().ok().flatten()
1034 } else {
1035 None
1036 }
1037 }
1038
1039 fn copy_to_selection(&self, text: &str) -> bool {
1040 if let Some(registry) = self.kernel.services.get::<ClipboardProviderRegistry>()
1041 && let Some(provider) = registry.get(&ClipboardKey::Default)
1042 {
1043 provider.copy_to_selection(text).is_ok()
1044 } else {
1045 false
1046 }
1047 }
1048
1049 fn paste_from_selection(&self) -> Option<String> {
1050 if let Some(registry) = self.kernel.services.get::<ClipboardProviderRegistry>()
1051 && let Some(provider) = registry.get(&ClipboardKey::Default)
1052 {
1053 provider.paste_from_selection().ok().flatten()
1054 } else {
1055 None
1056 }
1057 }
1058}
1059
1060impl SessionRuntime<'_> {
1061 pub fn push_to_clipboard_history(&mut self, content: RegisterContent) {
1067 self.clipboard_history.push(content);
1068 }
1069
1070 pub fn store_register_with_sync(&mut self, register: Option<char>, content: RegisterContent) {
1082 self.set_register(register, content.clone());
1083 match register {
1084 Some('+') => {
1085 self.copy_to_clipboard(&content.text);
1086 }
1087 Some('*') => {
1088 self.copy_to_selection(&content.text);
1089 }
1090 _ => {}
1091 }
1092 self.push_to_clipboard_history(content);
1093 }
1094
1095 pub fn get_register_with_clipboard(&self, register: Option<char>) -> Option<RegisterContent> {
1102 match register {
1103 Some('+') => self
1104 .paste_from_clipboard()
1105 .map(RegisterContent::characterwise)
1106 .or_else(|| self.get_register(register)),
1107 Some('*') => self
1108 .paste_from_selection()
1109 .map(RegisterContent::characterwise)
1110 .or_else(|| self.get_register(register)),
1111 _ => self.get_register(register),
1112 }
1113 }
1114}
1115
1116impl SessionRuntime<'_> {
1119 fn apply_undo_edits(&self, buffer: BufferId, edits: &[Edit]) {
1124 let Some(buf) = self.kernel.buffers.get(buffer) else {
1125 return;
1126 };
1127 let mut buf = buf.write();
1128 for edit in edits {
1129 match edit {
1130 Edit::Insert { position, text } => {
1131 buf.insert_at(*position, text);
1132 }
1133 Edit::Delete { position, text } => {
1134 buf.delete_at(*position, text.chars().count());
1135 }
1136 }
1137 }
1138 }
1139}
1140
1141impl UndoApi for SessionRuntime<'_> {
1142 #[cfg_attr(coverage_nightly, coverage(off))]
1143 fn undo(&mut self, buffer: BufferId) -> Option<UndoResult> {
1144 let undo_provider = self
1145 .kernel
1146 .services
1147 .get::<UndoProviderRegistry>()?
1148 .get(&UndoKey::Buffer)?;
1149
1150 let result = undo_provider.undo(buffer)?;
1151 self.apply_undo_edits(buffer, &result.edits);
1152
1153 if let Some(window) = self.windows_mut().active_mut() {
1155 window.cursor.line = result.cursor.line;
1156 window.cursor.column = result.cursor.column;
1157 }
1158
1159 self.changes.record_buffer_modified(buffer);
1160 self.changes.record_cursor_move(buffer);
1161
1162 Some(result)
1163 }
1164
1165 #[cfg_attr(coverage_nightly, coverage(off))]
1166 fn redo(&mut self, buffer: BufferId) -> Option<UndoResult> {
1167 let undo_provider = self
1168 .kernel
1169 .services
1170 .get::<UndoProviderRegistry>()?
1171 .get(&UndoKey::Buffer)?;
1172
1173 let result = undo_provider.redo(buffer)?;
1174 self.apply_undo_edits(buffer, &result.edits);
1175
1176 if let Some(window) = self.windows_mut().active_mut() {
1178 window.cursor.line = result.cursor.line;
1179 window.cursor.column = result.cursor.column;
1180 }
1181
1182 self.changes.record_buffer_modified(buffer);
1183 self.changes.record_cursor_move(buffer);
1184
1185 Some(result)
1186 }
1187
1188 #[cfg_attr(coverage_nightly, coverage(off))]
1189 fn record_edit(
1190 &mut self,
1191 buffer: BufferId,
1192 edits: Vec<Edit>,
1193 cursor_before: Position,
1194 cursor_after: Position,
1195 ) {
1196 if let Some(undo_registry) = self.kernel.services.get::<UndoProviderRegistry>()
1197 && let Some(undo_provider) = undo_registry.get(&UndoKey::Buffer)
1198 {
1199 undo_provider.record(buffer, edits, cursor_before, cursor_after);
1200 }
1201 }
1202
1203 fn can_undo(&self, buffer: BufferId) -> bool {
1204 self.kernel
1205 .services
1206 .get::<UndoProviderRegistry>()
1207 .and_then(|registry| registry.get(&UndoKey::Buffer))
1208 .and_then(|provider| provider.get_tree(buffer))
1209 .is_some_and(|tree| tree.can_undo())
1210 }
1211
1212 fn can_redo(&self, buffer: BufferId) -> bool {
1213 self.kernel
1214 .services
1215 .get::<UndoProviderRegistry>()
1216 .and_then(|registry| registry.get(&UndoKey::Buffer))
1217 .and_then(|provider| provider.get_tree(buffer))
1218 .is_some_and(|tree| tree.can_redo())
1219 }
1220
1221 #[cfg_attr(coverage_nightly, coverage(off))]
1222 fn undo_mine(&mut self, buffer: BufferId) -> Option<UndoResult> {
1223 let client_id = self.owner?.as_usize();
1225
1226 let undo_provider = self
1227 .kernel
1228 .services
1229 .get::<UndoProviderRegistry>()?
1230 .get(&UndoKey::Buffer)?;
1231
1232 let result = undo_provider.undo_for_client(buffer, client_id)?;
1233 self.apply_undo_edits(buffer, &result.edits);
1234
1235 if let Some(window) = self.windows_mut().active_mut() {
1237 window.cursor.line = result.cursor.line;
1238 window.cursor.column = result.cursor.column;
1239 }
1240
1241 self.changes.record_buffer_modified(buffer);
1242 self.changes.record_cursor_move(buffer);
1243
1244 Some(result)
1245 }
1246
1247 #[cfg_attr(coverage_nightly, coverage(off))]
1248 fn redo_mine(&mut self, buffer: BufferId) -> Option<UndoResult> {
1249 let client_id = self.owner?.as_usize();
1251
1252 let undo_provider = self
1253 .kernel
1254 .services
1255 .get::<UndoProviderRegistry>()?
1256 .get(&UndoKey::Buffer)?;
1257
1258 let result = undo_provider.redo_for_client(buffer, client_id)?;
1259 self.apply_undo_edits(buffer, &result.edits);
1260
1261 if let Some(window) = self.windows_mut().active_mut() {
1263 window.cursor.line = result.cursor.line;
1264 window.cursor.column = result.cursor.column;
1265 }
1266
1267 self.changes.record_buffer_modified(buffer);
1268 self.changes.record_cursor_move(buffer);
1269
1270 Some(result)
1271 }
1272
1273 #[cfg_attr(coverage_nightly, coverage(off))]
1274 fn record_edit_mine(
1275 &mut self,
1276 buffer: BufferId,
1277 edits: Vec<Edit>,
1278 cursor_before: Position,
1279 cursor_after: Position,
1280 ) {
1281 if let Some(client_id) = self.owner {
1283 if let Some(undo_registry) = self.kernel.services.get::<UndoProviderRegistry>()
1284 && let Some(undo_provider) = undo_registry.get(&UndoKey::Buffer)
1285 {
1286 undo_provider.record_for_client(
1287 buffer,
1288 client_id.as_usize(),
1289 edits,
1290 cursor_before,
1291 cursor_after,
1292 );
1293 }
1294 } else {
1295 self.record_edit(buffer, edits, cursor_before, cursor_after);
1297 }
1298 }
1299}
1300
1301impl CommandApi for SessionRuntime<'_> {
1304 fn execute_command(&mut self, cmd: CommandId, ctx: CommandContext) -> CommandResult {
1305 if self.command_depth >= 16 {
1307 return CommandResult::Error(
1308 "command recursion limit exceeded (max depth: 16)".to_string(),
1309 );
1310 }
1311 self.command_depth += 1;
1312
1313 let handle = self.executor.get_handle(&cmd);
1317 let result = handle.map_or_else(
1318 || CommandResult::Error(format!("command not found: {cmd:?}")),
1319 |handle| handle.execute(self, &ctx),
1320 );
1321
1322 self.command_depth -= 1;
1323 result
1324 }
1325}
1326
1327impl ExtensionApi for SessionRuntime<'_> {
1336 fn ext<T: SessionExtension>(&self) -> Option<&T> {
1337 self.extensions.get::<T>()
1339 }
1340
1341 fn ext_mut<T: SessionExtension>(&mut self) -> &mut T {
1342 self.extensions.get_or_insert::<T>()
1344 }
1345
1346 fn shared_ext<T: SessionExtension>(&self) -> Option<&T> {
1347 self.shared_extensions.as_ref().and_then(|m| m.get::<T>())
1348 }
1349
1350 fn shared_ext_mut<T: SessionExtension>(&mut self) -> Option<&mut T> {
1351 self.shared_extensions
1352 .as_mut()
1353 .map(|m| m.get_or_insert::<T>())
1354 }
1355}
1356
1357impl ChangeTracker for SessionRuntime<'_> {
1360 fn take_changes(&mut self) -> StateChanges {
1361 std::mem::take(&mut self.changes)
1362 }
1363
1364 fn record_cursor_move(&mut self, buffer: BufferId) {
1365 #[allow(clippy::cast_possible_truncation)]
1369 if let Some(window) = self.windows.active() {
1370 let to = (window.cursor.line as u32, window.cursor.column as u32);
1371 let from = self.cursor_snapshot.unwrap_or(to);
1372 self.kernel.event_bus.emit(CursorMoved {
1373 buffer_id: buffer.as_usize() as u64,
1374 from,
1375 to,
1376 });
1377 self.cursor_snapshot = Some(to);
1378 }
1379
1380 self.changes.record_cursor_move(buffer);
1381 if let Some(window) = self.windows.active_mut()
1388 && let Some(ref mut sel) = window.selection
1389 {
1390 sel.end = Position::new(window.cursor.line, window.cursor.column + 1);
1391 self.changes.record_selection_change(buffer);
1392 }
1393 }
1394
1395 fn record_selection_change(&mut self, buffer: BufferId) {
1396 self.changes.record_selection_change(buffer);
1397 }
1398}
1399
1400const fn to_kernel_split_direction(dir: SplitDirection) -> KernelSplitDirection {
1404 match dir {
1405 SplitDirection::Horizontal => KernelSplitDirection::Horizontal,
1406 SplitDirection::Vertical => KernelSplitDirection::Vertical,
1407 }
1408}
1409
1410impl SessionRuntime<'_> {
1411 fn emit_layout_event(&self, kind: LayoutChangeKind) {
1413 let (window_count, focused_window) = self
1414 .compositor
1415 .as_ref()
1416 .map_or((0, None), |c| (c.window_count(), c.focused().map(|id| id.as_usize() as u64)));
1417 self.kernel.event_bus.emit(LayoutChanged {
1418 kind,
1419 window_count,
1420 focused_window,
1421 });
1422 }
1423}
1424
1425impl CompositorApi for SessionRuntime<'_> {
1426 fn navigate(&self, direction: NavigateDirection) -> Result<WindowId, CompositorError> {
1427 let compositor = self
1428 .compositor
1429 .as_ref()
1430 .ok_or(CompositorError::NoActiveLayer)?;
1431
1432 let active = compositor
1433 .active_layer()
1434 .ok_or(CompositorError::NoActiveLayer)?;
1435
1436 let layer = compositor
1437 .layer_compositor(active)
1438 .ok_or(CompositorError::LayerNotFound(active))?;
1439
1440 let from = layer.focused().ok_or(CompositorError::NoFocusedWindow)?;
1441
1442 layer
1443 .navigate_tiled(from, direction)
1444 .ok_or(CompositorError::NoNeighbor(direction))
1445 }
1446
1447 #[cfg_attr(coverage_nightly, coverage(off))]
1448 fn split(&mut self, direction: SplitDirection) -> Result<WindowId, CompositorError> {
1449 let compositor = self
1450 .compositor
1451 .as_mut()
1452 .ok_or(CompositorError::NoActiveLayer)?;
1453
1454 let active = compositor
1455 .active_layer()
1456 .ok_or(CompositorError::NoActiveLayer)?;
1457
1458 let layer = compositor
1459 .layer_compositor_mut(active)
1460 .ok_or(CompositorError::LayerNotFound(active))?;
1461
1462 let from = layer.focused().ok_or(CompositorError::NoFocusedWindow)?;
1463
1464 let new_window = layer
1465 .split_tiled(from, direction)
1466 .ok_or(CompositorError::NotEnoughRoom)?;
1467
1468 if let Some(source) = self.windows.get(from) {
1472 let win = Window::split_from(new_window, source);
1473 self.windows.add(win);
1474 }
1475
1476 if let Some(compositor) = self.compositor.as_mut()
1479 && let Some(active) = compositor.active_layer()
1480 && let Some(layer) = compositor.layer_compositor_mut(active)
1481 {
1482 layer.set_focus(from);
1483 }
1484 self.windows.set_active(from);
1485
1486 self.changes.record_window_created(new_window);
1487
1488 self.emit_layout_event(LayoutChangeKind::Split {
1490 new_window: new_window.as_usize() as u64,
1491 direction: to_kernel_split_direction(direction),
1492 });
1493
1494 Ok(new_window)
1495 }
1496
1497 #[cfg_attr(coverage_nightly, coverage(off))]
1498 fn close_current_window(&mut self) -> Result<WindowId, CompositorError> {
1499 use reovim_driver_layout::Zone;
1500
1501 let compositor = self
1502 .compositor
1503 .as_mut()
1504 .ok_or(CompositorError::NoActiveLayer)?;
1505
1506 let active = compositor
1507 .active_layer()
1508 .ok_or(CompositorError::NoActiveLayer)?;
1509
1510 let layer = compositor
1511 .layer_compositor_mut(active)
1512 .ok_or(CompositorError::LayerNotFound(active))?;
1513
1514 let current = layer.focused().ok_or(CompositorError::NoFocusedWindow)?;
1515
1516 if layer.windows_in_zone(Zone::Tiled).len() <= 1 {
1518 return Err(CompositorError::CannotCloseLastWindow);
1519 }
1520
1521 let neighbor = layer
1522 .close_tiled(current)
1523 .ok_or(CompositorError::CannotCloseLastWindow)?;
1524
1525 self.windows.remove(current);
1527 self.windows.set_active(neighbor);
1528
1529 self.changes.record_window_closed(current);
1530
1531 self.emit_layout_event(LayoutChangeKind::Close {
1533 closed_window: current.as_usize() as u64,
1534 new_focus: Some(neighbor.as_usize() as u64),
1535 });
1536
1537 Ok(neighbor)
1538 }
1539
1540 #[cfg_attr(coverage_nightly, coverage(off))]
1541 fn close_others(&mut self) -> Result<(), CompositorError> {
1542 use reovim_driver_layout::Zone;
1543
1544 let compositor = self
1545 .compositor
1546 .as_mut()
1547 .ok_or(CompositorError::NoActiveLayer)?;
1548
1549 let active = compositor
1550 .active_layer()
1551 .ok_or(CompositorError::NoActiveLayer)?;
1552
1553 let layer = compositor
1554 .layer_compositor_mut(active)
1555 .ok_or(CompositorError::LayerNotFound(active))?;
1556
1557 let current = layer.focused().ok_or(CompositorError::NoFocusedWindow)?;
1558
1559 let windows: Vec<WindowId> = layer
1561 .windows_in_zone(Zone::Tiled)
1562 .into_iter()
1563 .filter(|&w| w != current)
1564 .collect();
1565
1566 for window in &windows {
1568 layer.close_tiled(*window);
1569 self.windows.remove(*window);
1570 self.changes.record_window_closed(*window);
1571 }
1572
1573 if let Some(&last_closed) = windows.last() {
1576 self.emit_layout_event(LayoutChangeKind::Close {
1577 closed_window: last_closed.as_usize() as u64,
1578 new_focus: Some(current.as_usize() as u64),
1579 });
1580 }
1581
1582 Ok(())
1583 }
1584
1585 fn resize(&mut self, direction: NavigateDirection, delta: i16) -> Result<(), CompositorError> {
1586 let compositor = self
1587 .compositor
1588 .as_mut()
1589 .ok_or(CompositorError::NoActiveLayer)?;
1590
1591 let active = compositor
1592 .active_layer()
1593 .ok_or(CompositorError::NoActiveLayer)?;
1594
1595 let layer = compositor
1596 .layer_compositor_mut(active)
1597 .ok_or(CompositorError::LayerNotFound(active))?;
1598
1599 let current = layer.focused().ok_or(CompositorError::NoFocusedWindow)?;
1600
1601 layer.resize_tiled(current, direction, delta);
1602 self.changes.window_changed = true;
1603
1604 self.emit_layout_event(LayoutChangeKind::Resize {
1606 window: current.as_usize() as u64,
1607 });
1608
1609 Ok(())
1610 }
1611
1612 fn equalize(&mut self) -> Result<(), CompositorError> {
1613 let compositor = self
1614 .compositor
1615 .as_mut()
1616 .ok_or(CompositorError::NoActiveLayer)?;
1617
1618 let active = compositor
1619 .active_layer()
1620 .ok_or(CompositorError::NoActiveLayer)?;
1621
1622 let layer = compositor
1623 .layer_compositor_mut(active)
1624 .ok_or(CompositorError::LayerNotFound(active))?;
1625
1626 layer.equalize_tiled();
1627 self.changes.window_changed = true;
1628
1629 self.emit_layout_event(LayoutChangeKind::Equalize);
1631
1632 Ok(())
1633 }
1634
1635 fn cycle(&self, forward: bool) -> Result<WindowId, CompositorError> {
1636 let compositor = self
1637 .compositor
1638 .as_ref()
1639 .ok_or(CompositorError::NoActiveLayer)?;
1640
1641 let active = compositor
1642 .active_layer()
1643 .ok_or(CompositorError::NoActiveLayer)?;
1644
1645 let layer = compositor
1646 .layer_compositor(active)
1647 .ok_or(CompositorError::LayerNotFound(active))?;
1648
1649 let from = layer.focused().ok_or(CompositorError::NoFocusedWindow)?;
1650
1651 layer
1652 .cycle_tiled(from, forward)
1653 .ok_or(CompositorError::NoFocusedWindow)
1654 }
1655
1656 #[cfg_attr(coverage_nightly, coverage(off))]
1657 fn focus(&mut self, window: WindowId) -> Result<(), CompositorError> {
1658 let compositor = self
1659 .compositor
1660 .as_mut()
1661 .ok_or(CompositorError::NoActiveLayer)?;
1662
1663 let previous_focus = compositor.focused();
1665
1666 compositor.set_focus(window);
1668
1669 self.windows.set_active(window);
1673 if let Some(buffer_id) = self.windows.active().and_then(|w| w.buffer_id) {
1674 *self.active_buffer = Some(buffer_id);
1675 }
1676
1677 self.changes.record_focus_change();
1678
1679 if previous_focus != Some(window) {
1681 self.emit_layout_event(LayoutChangeKind::Focus {
1682 from: previous_focus.map(|w| w.as_usize() as u64),
1683 to: window.as_usize() as u64,
1684 });
1685 }
1686
1687 Ok(())
1688 }
1689
1690 fn focused_window(&self) -> Option<WindowId> {
1691 self.compositor.as_ref()?.focused()
1692 }
1693
1694 fn compositor_window_count(&self) -> usize {
1695 self.compositor.as_ref().map_or(0, |c| c.window_count())
1696 }
1697
1698 fn arrange(&self, screen: Rect) -> Vec<WindowPlacement> {
1699 self.compositor
1700 .as_ref()
1701 .map_or_else(Vec::new, |c| c.composite(screen).placements)
1702 }
1703
1704 fn active_layer(&self) -> Option<LayerId> {
1705 self.compositor.as_ref()?.active_layer()
1706 }
1707
1708 fn set_screen(&mut self, screen: Rect) {
1709 self.screen = screen;
1710 if let Some(compositor) = self.compositor.as_mut() {
1711 compositor.set_screen(screen);
1712 }
1713 }
1714
1715 fn toggle_float(&mut self) -> Result<(), CompositorError> {
1720 let compositor = self
1721 .compositor
1722 .as_mut()
1723 .ok_or(CompositorError::NoActiveLayer)?;
1724
1725 let active = compositor
1726 .active_layer()
1727 .ok_or(CompositorError::NoActiveLayer)?;
1728
1729 let layer = compositor
1730 .layer_compositor_mut(active)
1731 .ok_or(CompositorError::LayerNotFound(active))?;
1732
1733 let current = layer.focused().ok_or(CompositorError::NoFocusedWindow)?;
1734
1735 layer.toggle_float(current);
1736 self.changes.window_changed = true;
1737
1738 Ok(())
1739 }
1740
1741 fn raise_float(&mut self) -> Result<(), CompositorError> {
1742 let compositor = self
1743 .compositor
1744 .as_mut()
1745 .ok_or(CompositorError::NoActiveLayer)?;
1746
1747 let active = compositor
1748 .active_layer()
1749 .ok_or(CompositorError::NoActiveLayer)?;
1750
1751 let layer = compositor
1752 .layer_compositor_mut(active)
1753 .ok_or(CompositorError::LayerNotFound(active))?;
1754
1755 let current = layer.focused().ok_or(CompositorError::NoFocusedWindow)?;
1756
1757 layer.raise_float(current);
1758
1759 Ok(())
1760 }
1761
1762 fn lower_float(&mut self) -> Result<(), CompositorError> {
1763 let compositor = self
1764 .compositor
1765 .as_mut()
1766 .ok_or(CompositorError::NoActiveLayer)?;
1767
1768 let active = compositor
1769 .active_layer()
1770 .ok_or(CompositorError::NoActiveLayer)?;
1771
1772 let layer = compositor
1773 .layer_compositor_mut(active)
1774 .ok_or(CompositorError::LayerNotFound(active))?;
1775
1776 let current = layer.focused().ok_or(CompositorError::NoFocusedWindow)?;
1777
1778 layer.lower_float(current);
1779
1780 Ok(())
1781 }
1782
1783 fn show_overlay(
1788 &mut self,
1789 constraints: OverlayConstraints,
1790 ) -> Result<WindowId, CompositorError> {
1791 let compositor = self
1792 .compositor
1793 .as_mut()
1794 .ok_or(CompositorError::NoActiveLayer)?;
1795
1796 let active = compositor
1797 .active_layer()
1798 .ok_or(CompositorError::NoActiveLayer)?;
1799
1800 let layer = compositor
1801 .layer_compositor_mut(active)
1802 .ok_or(CompositorError::LayerNotFound(active))?;
1803
1804 let id = layer.show_overlay(constraints);
1805 Ok(id)
1807 }
1808
1809 fn hide_overlay(&mut self, window: WindowId) -> Result<(), CompositorError> {
1810 let compositor = self
1811 .compositor
1812 .as_mut()
1813 .ok_or(CompositorError::NoActiveLayer)?;
1814
1815 let active = compositor
1816 .active_layer()
1817 .ok_or(CompositorError::NoActiveLayer)?;
1818
1819 let layer = compositor
1820 .layer_compositor_mut(active)
1821 .ok_or(CompositorError::LayerNotFound(active))?;
1822
1823 layer.hide_overlay(window);
1824 Ok(())
1825 }
1826
1827 fn resize_overlay(
1828 &mut self,
1829 window: WindowId,
1830 width: u16,
1831 height: u16,
1832 ) -> Result<(), CompositorError> {
1833 let compositor = self
1834 .compositor
1835 .as_mut()
1836 .ok_or(CompositorError::NoActiveLayer)?;
1837
1838 let active = compositor
1839 .active_layer()
1840 .ok_or(CompositorError::NoActiveLayer)?;
1841
1842 let layer = compositor
1843 .layer_compositor_mut(active)
1844 .ok_or(CompositorError::LayerNotFound(active))?;
1845
1846 layer.resize_overlay(window, width, height);
1847 Ok(())
1848 }
1849
1850 fn hide_all_overlays(&mut self) -> Result<(), CompositorError> {
1851 let compositor = self
1852 .compositor
1853 .as_mut()
1854 .ok_or(CompositorError::NoActiveLayer)?;
1855
1856 let active = compositor
1857 .active_layer()
1858 .ok_or(CompositorError::NoActiveLayer)?;
1859
1860 let layer = compositor
1861 .layer_compositor_mut(active)
1862 .ok_or(CompositorError::LayerNotFound(active))?;
1863
1864 layer.hide_all_overlays();
1865 Ok(())
1866 }
1867
1868 fn set_active_layer_opacity(&mut self, opacity: f32) -> Result<(), CompositorError> {
1873 let compositor = self
1874 .compositor
1875 .as_mut()
1876 .ok_or(CompositorError::NoActiveLayer)?;
1877
1878 let active = compositor
1879 .active_layer()
1880 .ok_or(CompositorError::NoActiveLayer)?;
1881
1882 compositor.set_layer_opacity(active, opacity.clamp(0.0, 1.0));
1883 self.changes.window_changed = true;
1884 Ok(())
1885 }
1886
1887 fn active_layer_opacity(&self) -> Result<f32, CompositorError> {
1888 let compositor = self
1889 .compositor
1890 .as_ref()
1891 .ok_or(CompositorError::NoActiveLayer)?;
1892
1893 let active = compositor
1894 .active_layer()
1895 .ok_or(CompositorError::NoActiveLayer)?;
1896
1897 let opacity = compositor
1898 .layers()
1899 .iter()
1900 .find(|l| l.id == active)
1901 .map_or(1.0, |l| l.opacity);
1902 Ok(opacity)
1903 }
1904
1905 fn adjust_active_layer_opacity(&mut self, delta: f32) -> Result<f32, CompositorError> {
1906 let current = self.active_layer_opacity()?;
1907 let new_opacity = (current + delta).clamp(0.0, 1.0);
1908 self.set_active_layer_opacity(new_opacity)?;
1909 Ok(new_opacity)
1910 }
1911
1912 fn tab_new(&mut self) -> Result<TabId, CompositorError> {
1919 let id = self.tabs.new_tab();
1920 self.changes.window_changed = true;
1921 Ok(id)
1922 }
1923
1924 fn tab_close(&mut self) -> Result<(), CompositorError> {
1925 if self.tabs.close_tab() {
1926 self.changes.window_changed = true;
1927 Ok(())
1928 } else {
1929 Err(CompositorError::CannotCloseLastTab)
1930 }
1931 }
1932
1933 fn tab_next(&mut self) -> Result<TabId, CompositorError> {
1934 let id = self.tabs.next_tab();
1935 self.changes.window_changed = true;
1936 Ok(id)
1937 }
1938
1939 fn tab_prev(&mut self) -> Result<TabId, CompositorError> {
1940 let id = self.tabs.prev_tab();
1941 self.changes.window_changed = true;
1942 Ok(id)
1943 }
1944
1945 fn tab_goto(&mut self, index: usize) -> Result<TabId, CompositorError> {
1946 self.tabs
1947 .goto_tab(index)
1948 .ok_or_else(|| CompositorError::TabNotFound(TabId::from_raw(index)))
1949 .inspect(|_| {
1950 self.changes.window_changed = true;
1951 })
1952 }
1953
1954 fn tab_count(&self) -> usize {
1955 self.tabs.tab_count()
1956 }
1957
1958 fn active_tab_id(&self) -> Option<TabId> {
1959 Some(self.tabs.active_tab_id())
1960 }
1961}
1962
1963fn char_col_to_byte(line: &str, col: usize) -> usize {
1965 line.char_indices().nth(col).map_or(line.len(), |(b, _)| b)
1966}
1967
1968#[cfg(test)]
1969#[path = "runtime_tests.rs"]
1970mod tests;