1pub mod buffers;
43pub mod process_group;
44
45pub use buffers::WindowBuffers;
46pub use process_group::{LocalSignaller, ProcessGroupEntry, ProcessGroups, Signaller};
47
48use crate::app::types::{ChromeLayout, WindowLayoutCache};
49use crate::app::window_resources::WindowResources;
50use crate::model::event::{Event, LeafId};
51use crate::services::lsp::manager::LspManager;
52use crate::types::LspFeature;
53use crate::view::file_tree::FileTreeView;
54use crate::view::split::{SplitManager, SplitViewState};
55use fresh_core::{BufferId, WindowId};
56use std::collections::HashMap;
57use std::path::PathBuf;
58use std::sync::Arc;
59
60pub struct Window {
70 pub id: WindowId,
72
73 pub label: String,
77
78 pub root: PathBuf,
82
83 pub file_explorer: Option<FileTreeView>,
88
89 pub file_mod_times: HashMap<PathBuf, std::time::SystemTime>,
95
96 pub lsp: Option<LspManager>,
106
107 pub panel_ids: HashMap<String, BufferId>,
112
113 pub buffers: WindowBuffers,
118
119 pub buffer_metadata: HashMap<BufferId, crate::app::types::BufferMetadata>,
125
126 pub event_logs: HashMap<BufferId, crate::model::event::EventLog>,
130
131 pub status_message: Option<String>,
137
138 pub plugin_status_message: Option<String>,
141
142 pub prompt: Option<crate::view::prompt::Prompt>,
146
147 pub bridge: crate::services::async_bridge::AsyncBridge,
156
157 pub next_lsp_request_id: u64,
163
164 pub pending_completion_requests: std::collections::HashSet<u64>,
166
167 pub completion_items: Option<Vec<lsp_types::CompletionItem>>,
169
170 pub scheduled_completion_trigger: Option<std::time::Instant>,
172
173 pub dabbrev_state: Option<crate::app::DabbrevCycleState>,
175
176 pub pending_goto_definition_request: Option<u64>,
178
179 pub pending_references_request: Option<u64>,
181 pub pending_references_symbol: String,
182
183 pub pending_signature_help_request: Option<u64>,
185
186 pub pending_code_actions_requests: std::collections::HashSet<u64>,
189 pub pending_code_actions_server_names: std::collections::HashMap<u64, String>,
190 pub pending_code_actions: Option<Vec<(String, lsp_types::CodeActionOrCommand)>>,
191
192 pub(crate) pending_inlay_hints_requests:
194 std::collections::HashMap<u64, crate::app::InlayHintsRequest>,
195
196 pub(crate) pending_folding_range_requests:
198 std::collections::HashMap<u64, crate::app::FoldingRangeRequest>,
199 pub folding_ranges_in_flight: std::collections::HashMap<BufferId, (u64, u64)>,
200 pub folding_ranges_debounce: std::collections::HashMap<BufferId, std::time::Instant>,
201
202 pub(crate) pending_semantic_token_requests:
205 std::collections::HashMap<u64, crate::app::SemanticTokenFullRequest>,
206 pub(crate) semantic_tokens_in_flight:
207 std::collections::HashMap<BufferId, (u64, u64, crate::app::SemanticTokensFullRequestKind)>,
208 pub semantic_tokens_full_debounce: std::collections::HashMap<BufferId, std::time::Instant>,
209
210 pub(crate) pending_semantic_token_range_requests:
213 std::collections::HashMap<u64, crate::app::SemanticTokenRangeRequest>,
214 pub semantic_tokens_range_in_flight:
215 std::collections::HashMap<BufferId, (u64, usize, usize, u64)>,
216 pub semantic_tokens_range_last_request:
217 std::collections::HashMap<BufferId, (usize, usize, u64, std::time::Instant)>,
218 pub semantic_tokens_range_applied: std::collections::HashMap<BufferId, (usize, usize, u64)>,
219
220 pub position_history: crate::input::position_history::PositionHistory,
226
227 pub in_navigation: bool,
232
233 pub suppress_position_history_once: bool,
237
238 pub(crate) bookmarks: crate::app::bookmarks::BookmarkState,
243
244 pub composite_buffers: HashMap<BufferId, crate::model::composite_buffer::CompositeBuffer>,
250
251 pub composite_view_states:
255 HashMap<(LeafId, BufferId), crate::view::composite_view::CompositeViewState>,
256
257 pub grouped_subtrees: HashMap<LeafId, crate::view::split::SplitNode>,
267
268 pub terminal_manager: crate::services::terminal::TerminalManager,
272
273 pub terminal_buffers: HashMap<BufferId, crate::services::terminal::TerminalId>,
275
276 pub terminal_backing_files: HashMap<crate::services::terminal::TerminalId, std::path::PathBuf>,
279
280 pub terminal_log_files: HashMap<crate::services::terminal::TerminalId, std::path::PathBuf>,
283
284 pub plugin_state: HashMap<String, HashMap<String, serde_json::Value>>,
290
291 pub(crate) layout_cache: WindowLayoutCache,
300
301 pub(crate) chrome_layout: ChromeLayout,
307
308 pub(crate) terminal_width: u16,
314 pub(crate) terminal_height: u16,
315
316 pub(crate) resources: WindowResources,
321
322 pub preview: Option<(LeafId, BufferId)>,
333
334 pub terminal_mode: bool,
338
339 pub terminal_mode_resume: std::collections::HashSet<BufferId>,
343
344 pub seen_byte_ranges: HashMap<BufferId, std::collections::HashSet<(usize, usize)>>,
348
349 pub previous_viewports: HashMap<LeafId, (usize, u16, u16)>,
354
355 pub same_buffer_scroll_sync: bool,
358
359 pub(crate) interactive_replace_state: Option<crate::app::types::InteractiveReplaceState>,
364
365 pub scroll_sync_manager: crate::view::scroll_sync::ScrollSyncManager,
368
369 pub file_explorer_visible: bool,
371
372 pub file_explorer_sync_in_progress: bool,
374
375 pub file_explorer_width: crate::config::ExplorerWidth,
377
378 pub file_explorer_side: crate::config::FileExplorerSide,
380
381 pub pending_file_explorer_show_hidden: Option<bool>,
384 pub pending_file_explorer_show_gitignored: Option<bool>,
385
386 pub file_explorer_decorations:
389 HashMap<String, Vec<crate::view::file_tree::FileExplorerDecoration>>,
390
391 pub file_explorer_decoration_cache: crate::view::file_tree::FileExplorerDecorationCache,
394
395 pub(crate) hover: crate::app::hover::HoverState,
399
400 pub(crate) search_state: Option<crate::app::types::SearchState>,
402
403 pub search_namespace: crate::view::overlay::OverlayNamespace,
406
407 pub pending_search_range: Option<std::ops::Range<usize>>,
410
411 pub live_grep_last_state: Option<crate::services::live_grep_state::LiveGrepLastState>,
414
415 pub overlay_preview_state: Option<crate::app::types::OverlayPreviewState>,
418
419 pub auto_revert_enabled: bool,
422
423 pub file_rapid_change_counts: HashMap<PathBuf, (std::time::Instant, u32)>,
426
427 pub(crate) goto_line_preview: Option<crate::app::GotoLinePreviewSnapshot>,
430
431 pub pending_async_prompt_callback: Option<fresh_core::api::JsCallbackId>,
434
435 pub pending_quit_unnamed_save: Vec<BufferId>,
438
439 pub search_case_sensitive: bool,
442 pub search_whole_word: bool,
443 pub search_use_regex: bool,
444 pub search_confirm_each: bool,
445
446 pub scheduled_diagnostic_pull: Option<(BufferId, std::time::Instant)>,
450 pub scheduled_inlay_hints_request: Option<(BufferId, std::time::Instant)>,
451
452 pub user_dismissed_lsp_languages: std::collections::HashSet<String>,
456
457 pub editor_mode: Option<String>,
461
462 pub prompt_histories: HashMap<String, crate::input::input_history::InputHistory>,
466
467 pub pending_close_buffer: Option<BufferId>,
470
471 pub completion_service: crate::services::completion::CompletionService,
476
477 pub lsp_diagnostic_namespace: crate::view::overlay::OverlayNamespace,
481
482 pub diagnostic_result_ids: HashMap<String, String>,
486
487 pub(crate) lsp_progress: HashMap<String, crate::app::LspProgressInfo>,
491
492 pub lsp_server_statuses:
495 HashMap<(String, String), crate::services::async_bridge::LspServerStatus>,
496
497 pub lsp_menu_contributions: HashMap<(String, String), Vec<crate::app::LspMenuItem>>,
512
513 pub(crate) lsp_window_messages: Vec<crate::app::LspMessageEntry>,
517
518 pub(crate) lsp_log_messages: Vec<crate::app::LspMessageEntry>,
521
522 pub stored_push_diagnostics: HashMap<String, HashMap<String, Vec<lsp_types::Diagnostic>>>,
527
528 pub stored_pull_diagnostics: HashMap<String, Vec<lsp_types::Diagnostic>>,
533
534 pub stored_diagnostics: Arc<HashMap<String, Vec<lsp_types::Diagnostic>>>,
538
539 pub stored_folding_ranges: Arc<HashMap<String, Vec<lsp_types::FoldingRange>>>,
543
544 pub dir_mod_times: HashMap<PathBuf, std::time::SystemTime>,
548
549 pub last_auto_revert_poll: std::time::Instant,
551
552 pub last_file_tree_poll: std::time::Instant,
554
555 pub git_index_resolved: bool,
558
559 #[allow(clippy::type_complexity)]
562 pub pending_file_poll_rx:
563 Option<std::sync::mpsc::Receiver<Vec<(PathBuf, Option<std::time::SystemTime>)>>>,
564
565 #[allow(clippy::type_complexity)]
567 pub pending_dir_poll_rx: Option<
568 std::sync::mpsc::Receiver<(
569 Vec<(
570 crate::view::file_tree::NodeId,
571 PathBuf,
572 Option<std::time::SystemTime>,
573 )>,
574 Option<(PathBuf, std::time::SystemTime)>,
575 )>,
576 >,
577
578 pub ephemeral_terminals: std::collections::HashSet<crate::services::terminal::TerminalId>,
582
583 pub plugin_dev_workspaces:
587 HashMap<BufferId, crate::services::plugins::plugin_dev_workspace::PluginDevWorkspace>,
588
589 pub(crate) mouse_state: crate::app::types::MouseState,
592
593 pub key_context: crate::input::keybindings::KeyContext,
598
599 pub chord_state: Vec<(crossterm::event::KeyCode, crossterm::event::KeyModifiers)>,
602
603 pub previous_click_time: Option<std::time::Instant>,
606 pub previous_click_position: Option<(u16, u16)>,
607 pub click_count: u8,
608
609 pub mouse_enabled: bool,
611
612 pub mouse_cursor_position: Option<(u16, u16)>,
615 pub gpm_active: bool,
616
617 pub menu_bar_visible: bool,
620 pub menu_bar_auto_shown: bool,
621 pub tab_bar_visible: bool,
622 pub status_bar_visible: bool,
623 pub prompt_line_visible: bool,
624
625 pub last_auto_recovery_save: std::time::Instant,
628 pub last_persistent_auto_save: std::time::Instant,
629
630 pub warning_domains: crate::app::warning_domains::WarningDomainRegistry,
632
633 pub tab_context_menu: Option<crate::app::types::TabContextMenu>,
635
636 pub file_explorer_context_menu: Option<crate::app::types::FileExplorerContextMenu>,
638
639 pub theme_info_popup: Option<crate::app::types::ThemeInfoPopup>,
641
642 pub event_debug: Option<crate::app::event_debug::EventDebug>,
646
647 pub file_open_state: Option<crate::app::file_open::FileOpenState>,
650
651 pub file_browser_layout: Option<crate::view::ui::FileBrowserLayout>,
653
654 pub buffer_groups: HashMap<crate::app::types::BufferGroupId, crate::app::types::BufferGroup>,
656 pub buffer_to_group: HashMap<BufferId, crate::app::types::BufferGroupId>,
658 pub next_buffer_group_id: usize,
660
661 pub pending_next_key_callbacks: std::collections::VecDeque<fresh_core::api::JsCallbackId>,
663
664 pub key_capture_active: bool,
666
667 pub pending_key_capture_buffer: std::collections::VecDeque<fresh_core::api::KeyEventPayload>,
670
671 pub(crate) macros: crate::app::macros::MacroState,
674
675 pub active_custom_contexts: std::collections::HashSet<String>,
678
679 pub keyboard_capture: bool,
682
683 pub review_hunks: Vec<fresh_core::api::ReviewHunk>,
685
686 pub pending_file_opens: Vec<crate::app::PendingFileOpen>,
688
689 pub pending_hot_exit_recovery: bool,
691
692 pub wait_tracking: HashMap<BufferId, (u64, bool)>,
694
695 pub completed_waits: Vec<u64>,
697
698 pub(crate) line_scan: crate::app::line_scan::LineScan,
701
702 pub(crate) search_scan: crate::app::search_scan::SearchScan,
704
705 pub search_overlay_top_byte: Option<usize>,
707
708 pub animations: crate::view::animation::AnimationRunner,
710
711 pub plugin_errors: Vec<String>,
714
715 pub file_explorer_clipboard: Option<crate::app::file_explorer::FileExplorerClipboard>,
719
720 pub process_groups: ProcessGroups,
728}
729
730impl Window {
731 pub fn apply_folding_ranges_response(
736 &mut self,
737 buffer_id: BufferId,
738 lsp_ranges: Vec<lsp_types::FoldingRange>,
739 ) {
740 let Some(state) = self.buffers.get_mut(&buffer_id) else {
741 return;
742 };
743 state
744 .folding_ranges
745 .set_from_lsp(&state.buffer, &mut state.marker_list, lsp_ranges);
746 }
747
748 pub fn alloc_lsp_request_id(&mut self) -> u64 {
752 let id = self.next_lsp_request_id;
753 self.next_lsp_request_id += 1;
754 id
755 }
756
757 pub fn has_pending_lsp_requests(&self) -> bool {
760 !self.pending_completion_requests.is_empty()
761 || self.pending_goto_definition_request.is_some()
762 }
763
764 pub(crate) fn cancel_pending_lsp_requests(&mut self) {
770 self.scheduled_completion_trigger = None;
771 if !self.pending_completion_requests.is_empty() {
772 let ids: Vec<u64> = self.pending_completion_requests.drain().collect();
773 for request_id in ids {
774 tracing::debug!("Canceling pending LSP completion request {}", request_id);
775 self.send_lsp_cancel_request(request_id);
776 }
777 }
778 if let Some(request_id) = self.pending_goto_definition_request.take() {
779 tracing::debug!(
780 "Canceling pending LSP goto-definition request {}",
781 request_id
782 );
783 self.send_lsp_cancel_request(request_id);
784 }
785 }
786
787 pub(crate) fn send_lsp_cancel_request(&mut self, request_id: u64) {
791 let buffer_id = self.active_buffer();
792 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
793 return;
794 };
795 if let Some(lsp) = self.lsp.as_mut() {
796 if let Some(handle) = lsp.get_handle_mut(&language) {
797 if let Err(e) = handle.cancel_request(request_id) {
798 tracing::warn!("Failed to send LSP cancel request: {}", e);
799 } else {
800 tracing::debug!("Sent $/cancelRequest for request_id={}", request_id);
801 }
802 }
803 }
804 }
805
806 pub fn toggle_tab_bar(&mut self) {
808 self.tab_bar_visible = !self.tab_bar_visible;
809 let key = if self.tab_bar_visible {
810 "toggle.tab_bar_shown"
811 } else {
812 "toggle.tab_bar_hidden"
813 };
814 self.set_status_message(rust_i18n::t!(key).to_string());
815 }
816
817 pub fn toggle_status_bar(&mut self) {
819 self.status_bar_visible = !self.status_bar_visible;
820 let key = if self.status_bar_visible {
821 "toggle.status_bar_shown"
822 } else {
823 "toggle.status_bar_hidden"
824 };
825 self.set_status_message(rust_i18n::t!(key).to_string());
826 }
827
828 pub fn toggle_prompt_line(&mut self) {
830 self.prompt_line_visible = !self.prompt_line_visible;
831 let key = if self.prompt_line_visible {
832 "toggle.prompt_line_shown"
833 } else {
834 "toggle.prompt_line_hidden"
835 };
836 self.set_status_message(rust_i18n::t!(key).to_string());
837 }
838
839 pub fn toggle_scroll_sync(&mut self) {
842 self.same_buffer_scroll_sync = !self.same_buffer_scroll_sync;
843 let key = if self.same_buffer_scroll_sync {
844 "toggle.scroll_sync_enabled"
845 } else {
846 "toggle.scroll_sync_disabled"
847 };
848 self.set_status_message(rust_i18n::t!(key).to_string());
849 }
850
851 pub fn toggle_debug_highlights(&mut self) {
855 let buffer_id = self.active_buffer();
856 if let Some(state) = self.buffers.get_mut(&buffer_id) {
857 state.debug_highlight_mode = !state.debug_highlight_mode;
858 let key = if state.debug_highlight_mode {
859 "toggle.debug_mode_on"
860 } else {
861 "toggle.debug_mode_off"
862 };
863 self.set_status_message(rust_i18n::t!(key).to_string());
864 }
865 }
866
867 pub(crate) fn build_search_regex(&self, query: &str) -> Result<regex::Regex, String> {
872 crate::app::regex_replace::build_search_regex(
873 query,
874 self.search_use_regex,
875 self.search_whole_word,
876 self.search_case_sensitive,
877 )
878 }
879
880 pub fn is_editing_disabled(&self) -> bool {
883 self.active_state().editing_disabled
884 }
885
886 pub(super) fn update_modified_from_event_log(&mut self) {
890 let buffer_id = self.active_buffer();
891 let is_at_saved = self
892 .event_logs
893 .get(&buffer_id)
894 .map(|log| log.is_at_saved_position())
895 .unwrap_or(false);
896 if let Some(state) = self.buffers.get_mut(&buffer_id) {
897 state.buffer.set_modified(!is_at_saved);
898 }
899 }
900
901 pub fn is_lsp_language_user_dismissed(&self, language: &str) -> bool {
904 self.user_dismissed_lsp_languages.contains(language)
905 }
906
907 pub fn dismiss_lsp_language(&mut self, language: &str) {
910 self.user_dismissed_lsp_languages
911 .insert(language.to_string());
912 }
913
914 pub fn undismiss_lsp_language(&mut self, language: &str) {
917 self.user_dismissed_lsp_languages.remove(language);
918 }
919
920 pub(crate) fn server_supports_code_action_resolve(&self) -> bool {
923 let Some(language) = self
924 .buffers
925 .get(&self.active_buffer())
926 .map(|s| s.language.clone())
927 else {
928 return false;
929 };
930 if let Some(lsp) = self.lsp.as_ref() {
931 for sh in lsp.get_handles(&language) {
932 if sh.capabilities.code_action_resolve {
933 return true;
934 }
935 }
936 }
937 false
938 }
939
940 pub(crate) fn server_supports_completion_resolve(&self) -> bool {
943 let Some(language) = self
944 .buffers
945 .get(&self.active_buffer())
946 .map(|s| s.language.clone())
947 else {
948 return false;
949 };
950 if let Some(lsp) = self.lsp.as_ref() {
951 for sh in lsp.get_handles(&language) {
952 if sh.capabilities.completion_resolve {
953 return true;
954 }
955 }
956 }
957 false
958 }
959
960 pub(crate) fn server_supports_prepare_rename(&self) -> bool {
965 let Some(language) = self
966 .buffers
967 .get(&self.active_buffer())
968 .map(|s| s.language.clone())
969 else {
970 return false;
971 };
972 if let Some(lsp) = self.lsp.as_ref() {
973 for sh in lsp.get_handles(&language) {
974 if sh.capabilities.rename {
975 return true;
976 }
977 }
978 }
979 false
980 }
981
982 pub(crate) fn send_prepare_rename(&mut self) {
987 let cursor_pos = self.active_cursors().primary().position;
988 let (line, character) = self
989 .active_state()
990 .buffer
991 .position_to_lsp_position(cursor_pos);
992
993 let buffer_id = self.active_buffer();
994 let metadata = match self.buffer_metadata.get(&buffer_id) {
995 Some(m) if m.lsp_enabled => m,
996 _ => return,
997 };
998 let uri = match metadata.file_uri() {
999 Some(u) => u.clone(),
1000 None => return,
1001 };
1002 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
1003 return;
1004 };
1005
1006 let request_id = self.alloc_lsp_request_id();
1007
1008 if let Some(lsp) = self.lsp.as_mut() {
1009 if let Some(sh) = lsp.handle_for_feature_mut(&language, LspFeature::Rename) {
1010 if let Err(e) = sh.handle.prepare_rename(
1011 request_id,
1012 uri.as_uri().clone(),
1013 line as u32,
1014 character as u32,
1015 ) {
1016 tracing::warn!("Failed to send prepareRename: {}", e);
1017 }
1018 }
1019 }
1020 }
1021
1022 pub(crate) fn send_completion_resolve(&mut self, item: lsp_types::CompletionItem) {
1027 let Some(language) = self
1028 .buffers
1029 .get(&self.active_buffer())
1030 .map(|s| s.language.clone())
1031 else {
1032 return;
1033 };
1034 let request_id = self.alloc_lsp_request_id();
1035 if let Some(lsp) = self.lsp.as_mut() {
1036 for sh in lsp.get_handles_mut(&language) {
1037 if sh.capabilities.completion_resolve {
1038 if let Err(e) = sh.handle.completion_resolve(request_id, item.clone()) {
1039 tracing::warn!(
1040 "Failed to send completionItem/resolve to '{}': {}",
1041 sh.name,
1042 e
1043 );
1044 }
1045 return;
1046 }
1047 }
1048 }
1049 }
1050
1051 pub fn apply_event_to_buffer(
1057 &mut self,
1058 buffer_id: BufferId,
1059 split_id: LeafId,
1060 event: &crate::model::event::Event,
1061 ) {
1062 self.buffers
1063 .with_buffer_and_split(buffer_id, split_id, |state, vs| {
1064 state.apply(&mut vs.cursors, event);
1065 });
1066 }
1067
1068 pub fn apply_event_to_keyed_buffer(
1074 &mut self,
1075 buffer_id: BufferId,
1076 split_id: LeafId,
1077 event: &crate::model::event::Event,
1078 ) {
1079 self.buffers
1080 .with_buffer_and_split(buffer_id, split_id, |state, vs| {
1081 if let Some(keyed) = vs.keyed_states.get_mut(&buffer_id) {
1082 state.apply(&mut keyed.cursors, event);
1083 }
1084 });
1085 }
1086
1087 pub fn ensure_cursor_visible_for_split(&mut self, buffer_id: BufferId, split_id: LeafId) {
1092 self.buffers
1093 .with_buffer_and_split(buffer_id, split_id, |state, vs| {
1094 vs.ensure_cursor_visible(&mut state.buffer, &state.marker_list);
1095 });
1096 }
1097
1098 pub fn scroll_split_viewport_to(
1105 &mut self,
1106 buffer_id: BufferId,
1107 split_id: LeafId,
1108 target_line: usize,
1109 lock_against_ensure_visible: bool,
1110 ) {
1111 self.buffers
1112 .with_buffer_and_split(buffer_id, split_id, |state, vs| {
1113 vs.viewport.scroll_to(&mut state.buffer, target_line);
1114 if lock_against_ensure_visible {
1115 vs.viewport.set_skip_ensure_visible();
1116 }
1117 });
1118 }
1119
1120 pub fn add_fold(
1125 &mut self,
1126 buffer_id: BufferId,
1127 start: usize,
1128 end: usize,
1129 placeholder: Option<String>,
1130 ) -> bool {
1131 self.buffers
1132 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1133 for vs in vs_map.values_mut() {
1134 if vs.keyed_states.contains_key(&buffer_id) {
1135 let buf_state = vs.ensure_buffer_state(buffer_id);
1136 buf_state.folds.add(
1137 &mut state.marker_list,
1138 start,
1139 end,
1140 placeholder.clone(),
1141 );
1142 }
1143 }
1144 })
1145 .is_some()
1146 }
1147
1148 pub fn clear_folds(&mut self, buffer_id: BufferId) -> bool {
1151 self.buffers
1152 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1153 for vs in vs_map.values_mut() {
1154 if vs.keyed_states.contains_key(&buffer_id) {
1155 let buf_state = vs.ensure_buffer_state(buffer_id);
1156 buf_state.folds.clear(&mut state.marker_list);
1157 }
1158 }
1159 })
1160 .is_some()
1161 }
1162
1163 pub fn set_buffer_cursor_in_splits(
1170 &mut self,
1171 buffer_id: BufferId,
1172 position: usize,
1173 splits: &[LeafId],
1174 ) {
1175 self.buffers
1176 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1177 for leaf_id in splits {
1178 let Some(view_state) = vs_map.get_mut(leaf_id) else {
1179 continue;
1180 };
1181 view_state.cursors.primary_mut().move_to(position, false);
1182 view_state.ensure_cursor_visible(&mut state.buffer, &state.marker_list);
1183 }
1184 });
1185 }
1186
1187 pub fn set_split_scroll_to_byte(
1193 &mut self,
1194 buffer_id: BufferId,
1195 leaf_id: LeafId,
1196 top_byte: usize,
1197 ) {
1198 self.buffers
1199 .with_buffer_and_split(buffer_id, leaf_id, |state, view_state| {
1200 let total_bytes = state.buffer.len();
1201 let clamped_byte = top_byte.min(total_bytes);
1202 let target_line = state
1203 .buffer
1204 .offset_to_position(clamped_byte)
1205 .map(|p| p.line)
1206 .unwrap_or(0);
1207 view_state
1208 .viewport
1209 .scroll_to(&mut state.buffer, target_line);
1210 view_state.viewport.top_byte = clamped_byte;
1211 view_state.viewport.top_view_line_offset = 0;
1212 view_state.viewport.set_skip_ensure_visible();
1213 });
1214 }
1215
1216 pub fn scroll_buffer_to_line_in_splits(
1222 &mut self,
1223 buffer_id: BufferId,
1224 target_leaves: &[LeafId],
1225 line: usize,
1226 ) {
1227 self.buffers
1228 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1229 for leaf_id in target_leaves {
1230 let Some(view_state) = vs_map.get_mut(leaf_id) else {
1231 continue;
1232 };
1233 let viewport_height = view_state.viewport.height as usize;
1234 let lines_above = viewport_height / 3;
1235 let target = line.saturating_sub(lines_above);
1236 view_state.viewport.scroll_to(&mut state.buffer, target);
1237 view_state.viewport.set_skip_ensure_visible();
1238 }
1239 });
1240 }
1241
1242 pub fn restore_buffer_state_in_split(
1252 &mut self,
1253 buffer_id: BufferId,
1254 split_id: LeafId,
1255 file_state: &crate::workspace::SerializedFileState,
1256 ) {
1257 self.buffers
1258 .with_buffer_and_split(buffer_id, split_id, |buffer_state, vs| {
1259 let Some(buf_state) = vs.keyed_states.get_mut(&buffer_id) else {
1260 return;
1261 };
1262 let max_pos = buffer_state.buffer.len();
1263 let cursor_pos = file_state.cursor.position.min(max_pos);
1264 buf_state.cursors.primary_mut().position = cursor_pos;
1265 buf_state.cursors.primary_mut().anchor =
1266 file_state.cursor.anchor.map(|a| a.min(max_pos));
1267 buf_state.viewport.top_byte = file_state.scroll.top_byte;
1268 buf_state.viewport.left_column = file_state.scroll.left_column;
1269 crate::app::navigation::reconcile_restored_buffer_view(
1270 buf_state,
1271 &mut buffer_state.buffer,
1272 );
1273 });
1274 }
1275
1276 pub fn enter_terminal_scrollback_view(&mut self, buffer_id: BufferId, leaf_id: LeafId) {
1282 self.buffers
1283 .with_buffer_and_split(buffer_id, leaf_id, |state, view_state| {
1284 view_state.viewport.line_wrap_enabled = false;
1285 view_state.viewport.clear_skip_ensure_visible();
1286 view_state.ensure_cursor_visible(&mut state.buffer, &state.marker_list);
1287 });
1288 }
1289
1290 pub fn install_terminal_buffer_state(
1298 &mut self,
1299 buffer_id: BufferId,
1300 new_state: crate::state::EditorState,
1301 ) {
1302 self.buffers
1303 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1304 *state = new_state;
1305 let total = state.buffer.total_bytes();
1306 for vs in vs_map.values_mut() {
1307 if vs.has_buffer(buffer_id) {
1308 vs.cursors.primary_mut().position = total;
1309 }
1310 }
1311 state.buffer.set_modified(false);
1312 state.editing_disabled = true;
1313 state.margins.configure_for_line_numbers(false);
1314 });
1315 }
1316
1317 pub fn scroll_split_by_lines(
1325 &mut self,
1326 buffer_id: BufferId,
1327 leaf_id: LeafId,
1328 delta: i32,
1329 view_transform_tokens: Option<Vec<fresh_core::api::ViewTokenWire>>,
1330 tab_size: usize,
1331 ) {
1332 self.buffers
1333 .with_buffer_and_split(buffer_id, leaf_id, |state, view_state| {
1334 let soft_breaks = state.collect_soft_break_positions();
1335 let virtual_lines = state.collect_virtual_line_positions();
1336 let buffer = &mut state.buffer;
1337 let top_byte_before = view_state.viewport.top_byte;
1338 if let Some(tokens) = view_transform_tokens {
1339 use crate::view::ui::view_pipeline::ViewLineIterator;
1340 let view_lines: Vec<_> =
1341 ViewLineIterator::new(&tokens, false, false, tab_size, false).collect();
1342 view_state
1343 .viewport
1344 .scroll_view_lines(&view_lines, delta as isize);
1345 } else if delta < 0 {
1346 let lines_to_scroll = delta.unsigned_abs() as usize;
1347 view_state.viewport.scroll_up(
1348 buffer,
1349 &soft_breaks,
1350 &virtual_lines,
1351 lines_to_scroll,
1352 );
1353 } else {
1354 let lines_to_scroll = delta as usize;
1355 view_state.viewport.scroll_down(
1356 buffer,
1357 &soft_breaks,
1358 &virtual_lines,
1359 lines_to_scroll,
1360 );
1361 }
1362 view_state.viewport.set_skip_ensure_visible();
1363
1364 if let Some(folds) = view_state.keyed_states.get(&buffer_id).map(|bs| &bs.folds) {
1365 if !folds.is_empty() {
1366 let top_line = buffer.get_line_number(view_state.viewport.top_byte);
1367 if let Some(range) = folds
1368 .resolved_ranges(buffer, &state.marker_list)
1369 .iter()
1370 .find(|r| top_line >= r.start_line && top_line <= r.end_line)
1371 {
1372 let target_line = if delta >= 0 {
1373 range.end_line.saturating_add(1)
1374 } else {
1375 range.header_line
1376 };
1377 let target_byte = buffer
1378 .line_start_offset(target_line)
1379 .unwrap_or_else(|| buffer.len());
1380 view_state.viewport.top_byte = target_byte;
1381 view_state.viewport.top_view_line_offset = 0;
1382 }
1383 }
1384 }
1385 tracing::trace!(
1386 "scroll_split_by_lines: delta={}, top_byte {} -> {}",
1387 delta,
1388 top_byte_before,
1389 view_state.viewport.top_byte
1390 );
1391 });
1392 }
1393
1394 pub fn clear_lsp_overlays_for_buffer(
1398 &mut self,
1399 buffer_id: BufferId,
1400 diagnostic_namespace: &crate::model::event::OverlayNamespace,
1401 ) {
1402 self.buffers
1403 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1404 state
1405 .overlays
1406 .clear_namespace(diagnostic_namespace, &mut state.marker_list);
1407 state.virtual_texts.clear(&mut state.marker_list);
1408 state.folding_ranges.clear(&mut state.marker_list);
1409 for view_state in vs_map.values_mut() {
1410 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
1411 buf_state.folds.clear(&mut state.marker_list);
1412 }
1413 }
1414 });
1415 }
1416
1417 pub fn split_manager_mut(&mut self) -> Option<&mut SplitManager> {
1423 self.buffers.split_manager_mut()
1424 }
1425
1426 pub fn split_view_states_mut(&mut self) -> Option<&mut HashMap<LeafId, SplitViewState>> {
1428 self.buffers.split_view_states_mut()
1429 }
1430
1431 pub fn splits_mut(
1434 &mut self,
1435 ) -> Option<(&mut SplitManager, &mut HashMap<LeafId, SplitViewState>)> {
1436 self.buffers.splits_mut().map(|(m, vs)| (m, vs))
1437 }
1438
1439 pub fn new(
1447 id: WindowId,
1448 label: impl Into<String>,
1449 root: PathBuf,
1450 resources: WindowResources,
1451 ) -> Self {
1452 let mut label = label.into();
1453 if label.is_empty() {
1454 label = root
1455 .file_name()
1456 .and_then(|n| n.to_str())
1457 .map(str::to_owned)
1458 .unwrap_or_else(|| "main".to_owned());
1459 }
1460 let now = resources.time_source.now();
1467 Self {
1468 id,
1469 label,
1470 root,
1471 file_explorer: None,
1472 file_mod_times: HashMap::new(),
1473 plugin_state: HashMap::new(),
1474 lsp: None,
1475 panel_ids: HashMap::new(),
1476 buffers: WindowBuffers::new(),
1477 buffer_metadata: HashMap::new(),
1478 terminal_manager: crate::services::terminal::TerminalManager::new(),
1479 terminal_buffers: HashMap::new(),
1480 terminal_backing_files: HashMap::new(),
1481 terminal_log_files: HashMap::new(),
1482 event_logs: HashMap::new(),
1483 status_message: None,
1484 plugin_status_message: None,
1485 prompt: None,
1486 bridge: crate::services::async_bridge::AsyncBridge::new(),
1487 next_lsp_request_id: 0,
1488 pending_completion_requests: std::collections::HashSet::new(),
1489 completion_items: None,
1490 scheduled_completion_trigger: None,
1491 dabbrev_state: None,
1492 pending_goto_definition_request: None,
1493 pending_references_request: None,
1494 pending_references_symbol: String::new(),
1495 pending_signature_help_request: None,
1496 pending_code_actions_requests: std::collections::HashSet::new(),
1497 pending_code_actions_server_names: std::collections::HashMap::new(),
1498 pending_code_actions: None,
1499 pending_inlay_hints_requests: std::collections::HashMap::new(),
1500 pending_folding_range_requests: std::collections::HashMap::new(),
1501 folding_ranges_in_flight: std::collections::HashMap::new(),
1502 folding_ranges_debounce: std::collections::HashMap::new(),
1503 pending_semantic_token_requests: std::collections::HashMap::new(),
1504 semantic_tokens_in_flight: std::collections::HashMap::new(),
1505 semantic_tokens_full_debounce: std::collections::HashMap::new(),
1506 pending_semantic_token_range_requests: std::collections::HashMap::new(),
1507 semantic_tokens_range_in_flight: std::collections::HashMap::new(),
1508 semantic_tokens_range_last_request: std::collections::HashMap::new(),
1509 semantic_tokens_range_applied: std::collections::HashMap::new(),
1510 position_history: crate::input::position_history::PositionHistory::new(),
1511 in_navigation: false,
1512 suppress_position_history_once: false,
1513 bookmarks: crate::app::bookmarks::BookmarkState::default(),
1514 grouped_subtrees: HashMap::new(),
1515 composite_buffers: HashMap::new(),
1516 composite_view_states: HashMap::new(),
1517 layout_cache: WindowLayoutCache::default(),
1518 chrome_layout: ChromeLayout::default(),
1519 terminal_width: 80,
1520 terminal_height: 24,
1521 preview: None,
1522 terminal_mode: false,
1523 terminal_mode_resume: std::collections::HashSet::new(),
1524 seen_byte_ranges: HashMap::new(),
1525 previous_viewports: HashMap::new(),
1526 same_buffer_scroll_sync: false,
1527 interactive_replace_state: None,
1528 scroll_sync_manager: crate::view::scroll_sync::ScrollSyncManager::new(),
1529 file_explorer_visible: false,
1530 file_explorer_sync_in_progress: false,
1531 file_explorer_width: resources.config.file_explorer.width,
1532 file_explorer_side: resources.config.file_explorer.side,
1533 pending_file_explorer_show_hidden: None,
1534 pending_file_explorer_show_gitignored: None,
1535 file_explorer_decorations: HashMap::new(),
1536 file_explorer_decoration_cache:
1537 crate::view::file_tree::FileExplorerDecorationCache::default(),
1538 hover: crate::app::hover::HoverState::default(),
1539 search_state: None,
1540 search_namespace: crate::view::overlay::OverlayNamespace::from_string(
1541 "search".to_string(),
1542 ),
1543 pending_search_range: None,
1544 live_grep_last_state: None,
1545 overlay_preview_state: None,
1546 auto_revert_enabled: true,
1547 file_rapid_change_counts: HashMap::new(),
1548 goto_line_preview: None,
1549 pending_async_prompt_callback: None,
1550 pending_quit_unnamed_save: Vec::new(),
1551 search_case_sensitive: true,
1552 search_whole_word: false,
1553 search_use_regex: false,
1554 search_confirm_each: false,
1555 scheduled_diagnostic_pull: None,
1556 scheduled_inlay_hints_request: None,
1557 user_dismissed_lsp_languages: std::collections::HashSet::new(),
1558 editor_mode: None,
1559 prompt_histories: HashMap::new(),
1560 pending_close_buffer: None,
1561 completion_service: crate::services::completion::CompletionService::new(),
1562 lsp_diagnostic_namespace: crate::view::overlay::OverlayNamespace::from_string(
1563 "lsp-diagnostic".to_string(),
1564 ),
1565 diagnostic_result_ids: HashMap::new(),
1566 lsp_progress: HashMap::new(),
1567 lsp_server_statuses: HashMap::new(),
1568 lsp_menu_contributions: HashMap::new(),
1569 lsp_window_messages: Vec::new(),
1570 lsp_log_messages: Vec::new(),
1571 stored_push_diagnostics: HashMap::new(),
1572 stored_pull_diagnostics: HashMap::new(),
1573 stored_diagnostics: Arc::new(HashMap::new()),
1574 stored_folding_ranges: Arc::new(HashMap::new()),
1575 dir_mod_times: HashMap::new(),
1576 last_auto_revert_poll: now,
1577 last_file_tree_poll: now,
1578 git_index_resolved: false,
1579 pending_file_poll_rx: None,
1580 pending_dir_poll_rx: None,
1581 ephemeral_terminals: std::collections::HashSet::new(),
1582 plugin_dev_workspaces: HashMap::new(),
1583 mouse_state: crate::app::types::MouseState::default(),
1584 key_context: crate::input::keybindings::KeyContext::Normal,
1585 chord_state: Vec::new(),
1586 previous_click_time: None,
1587 previous_click_position: None,
1588 click_count: 0,
1589 mouse_enabled: false,
1590 mouse_cursor_position: None,
1591 gpm_active: false,
1592 menu_bar_visible: resources.config.editor.show_menu_bar,
1593 menu_bar_auto_shown: false,
1594 tab_bar_visible: resources.config.editor.show_tab_bar,
1595 status_bar_visible: resources.config.editor.show_status_bar,
1596 prompt_line_visible: resources.config.editor.show_prompt_line,
1597 last_auto_recovery_save: now,
1598 last_persistent_auto_save: now,
1599 warning_domains: crate::app::warning_domains::WarningDomainRegistry::default(),
1600 tab_context_menu: None,
1601 file_explorer_context_menu: None,
1602 theme_info_popup: None,
1603 event_debug: None,
1604 file_open_state: None,
1605 file_browser_layout: None,
1606 buffer_groups: HashMap::new(),
1607 buffer_to_group: HashMap::new(),
1608 next_buffer_group_id: 0,
1609 pending_next_key_callbacks: std::collections::VecDeque::new(),
1610 key_capture_active: false,
1611 pending_key_capture_buffer: std::collections::VecDeque::new(),
1612 macros: crate::app::macros::MacroState::default(),
1613 active_custom_contexts: std::collections::HashSet::new(),
1614 keyboard_capture: false,
1615 review_hunks: Vec::new(),
1616 pending_file_opens: Vec::new(),
1617 pending_hot_exit_recovery: false,
1618 wait_tracking: HashMap::new(),
1619 completed_waits: Vec::new(),
1620 line_scan: crate::app::line_scan::LineScan::default(),
1621 search_scan: crate::app::search_scan::SearchScan::default(),
1622 search_overlay_top_byte: None,
1623 animations: crate::view::animation::AnimationRunner::default(),
1624 plugin_errors: Vec::new(),
1625 file_explorer_clipboard: None,
1626 process_groups: ProcessGroups::default(),
1627 resources,
1628 }
1629 }
1630
1631 pub fn config(&self) -> &crate::config::Config {
1641 &self.resources.config
1642 }
1643
1644 pub fn authority(&self) -> &crate::services::authority::Authority {
1646 &self.resources.authority
1647 }
1648
1649 pub fn alloc_buffer_id(&self) -> BufferId {
1651 self.resources.buffer_id_alloc.next()
1652 }
1653
1654 pub fn set_status_message(&mut self, message: String) {
1659 tracing::info!(target: "status", "{}", message);
1660 self.plugin_status_message = None;
1661 self.status_message = Some(message);
1662 }
1663
1664 pub fn clear_status_message(&mut self) {
1666 self.status_message = None;
1667 }
1668
1669 pub fn effective_active_pair(&self) -> (LeafId, BufferId) {
1679 let (mgr, vs_map) = self
1680 .buffers
1681 .splits()
1682 .expect("active window must have a populated split layout");
1683 let active_split = mgr.active_split();
1684 if let Some(vs) = vs_map.get(&active_split) {
1685 if vs.active_group_tab.is_some() {
1686 if let Some(inner_leaf) = vs.focused_group_leaf {
1687 if let Some(inner_vs) = vs_map.get(&inner_leaf) {
1688 let inner_buf = inner_vs.active_buffer;
1689 if self.buffers.get(&inner_buf).is_some()
1690 && inner_vs.keyed_states.contains_key(&inner_buf)
1691 {
1692 return (inner_leaf, inner_buf);
1693 }
1694 }
1695 }
1696 }
1697 }
1698 let outer_buf = mgr
1699 .active_buffer_id()
1700 .expect("Editor always has at least one buffer");
1701 if self.buffers.get(&outer_buf).is_some() {
1714 (active_split, outer_buf)
1715 } else if let Some(any) = self.buffers.find_id(|_, _| true) {
1716 tracing::warn!(
1717 stale_buffer_id = ?outer_buf,
1718 fallback_buffer_id = ?any,
1719 active_split = ?active_split,
1720 "effective_active_pair: split manager's active leaf points at \
1721 a BufferId missing from window.buffers (issue #1939). Falling \
1722 back to any live buffer; the split tree is in an inconsistent \
1723 state and should be repaired"
1724 );
1725 (active_split, any)
1726 } else {
1727 tracing::error!(
1731 stale_buffer_id = ?outer_buf,
1732 active_split = ?active_split,
1733 "effective_active_pair: window.buffers is empty AND the split \
1734 manager has a stale active buffer — no recovery possible, \
1735 next render will panic"
1736 );
1737 (active_split, outer_buf)
1738 }
1739 }
1740
1741 #[inline]
1743 pub fn active_buffer(&self) -> BufferId {
1744 let (_, buf) = self.effective_active_pair();
1745 buf
1746 }
1747
1748 pub fn effective_tabs_width(&self) -> u16 {
1752 if self.file_explorer_visible && self.file_explorer.is_some() {
1753 let explorer = self.file_explorer_width.to_cols(self.terminal_width);
1754 self.terminal_width.saturating_sub(explorer)
1755 } else {
1756 self.terminal_width
1757 }
1758 }
1759
1760 #[inline]
1763 pub fn effective_active_split(&self) -> LeafId {
1764 let (split, _) = self.effective_active_pair();
1765 split
1766 }
1767
1768 pub fn active_state(&self) -> &crate::state::EditorState {
1772 let buf = self.active_buffer();
1773 self.buffers
1774 .get(&buf)
1775 .expect("active buffer must be present in window")
1776 }
1777
1778 pub fn active_state_mut(&mut self) -> &mut crate::state::EditorState {
1780 let buf = self.active_buffer();
1781 self.buffers
1782 .get_mut(&buf)
1783 .expect("active buffer must be present in window")
1784 }
1785
1786 pub fn active_cursors(&self) -> &crate::model::cursor::Cursors {
1790 let split_id = self.effective_active_split();
1791 &self
1792 .buffers
1793 .splits()
1794 .expect("active window must have a populated split layout")
1795 .1
1796 .get(&split_id)
1797 .expect("active split must be in view-state map")
1798 .cursors
1799 }
1800
1801 pub fn active_cursors_mut(&mut self) -> &mut crate::model::cursor::Cursors {
1803 let split_id = self.effective_active_split();
1804 &mut self
1805 .buffers
1806 .splits_mut()
1807 .expect("active window must have a populated split layout")
1808 .1
1809 .get_mut(&split_id)
1810 .expect("active split must be in view-state map")
1811 .cursors
1812 }
1813
1814 pub fn active_event_log(&self) -> &crate::model::event::EventLog {
1816 let buf = self.active_buffer();
1817 self.event_logs
1818 .get(&buf)
1819 .expect("active buffer must have an event log")
1820 }
1821
1822 pub fn active_event_log_mut(&mut self) -> &mut crate::model::event::EventLog {
1824 let buf = self.active_buffer();
1825 self.event_logs
1826 .get_mut(&buf)
1827 .expect("active buffer must have an event log")
1828 }
1829
1830 pub fn promote_buffer_from_preview(&mut self, buffer_id: BufferId) {
1835 if let Some(m) = self.buffer_metadata.get_mut(&buffer_id) {
1836 m.is_preview = false;
1837 }
1838 if let Some((_, id)) = self.preview {
1839 if id == buffer_id {
1840 self.preview = None;
1841 }
1842 }
1843 }
1844
1845 pub fn promote_active_buffer_from_preview(&mut self) {
1848 let id = self.active_buffer();
1849 self.promote_buffer_from_preview(id);
1850 }
1851
1852 pub fn promote_current_preview(&mut self) {
1857 if let Some((_, id)) = self.preview.take() {
1858 if let Some(m) = self.buffer_metadata.get_mut(&id) {
1859 m.is_preview = false;
1860 }
1861 }
1862 }
1863
1864 pub fn promote_preview_if_not_in_split(&mut self, new_split: LeafId) {
1868 if let Some((preview_split, _)) = self.preview {
1869 if preview_split != new_split {
1870 self.promote_current_preview();
1871 }
1872 }
1873 }
1874
1875 pub fn is_buffer_preview(&self, buffer_id: BufferId) -> bool {
1880 self.buffer_metadata
1881 .get(&buffer_id)
1882 .map(|m| m.is_preview)
1883 .unwrap_or(false)
1884 }
1885
1886 pub fn current_preview(&self) -> Option<(LeafId, BufferId)> {
1889 self.preview
1890 }
1891
1892 pub fn is_terminal_buffer(&self, buffer_id: BufferId) -> bool {
1896 self.terminal_buffers.contains_key(&buffer_id)
1897 }
1898
1899 pub fn get_terminal_id(
1902 &self,
1903 buffer_id: BufferId,
1904 ) -> Option<crate::services::terminal::TerminalId> {
1905 self.terminal_buffers.get(&buffer_id).copied()
1906 }
1907
1908 pub fn clear_search_overlays(&mut self) {
1911 let ns = self.search_namespace.clone();
1912 let state = self.active_state_mut();
1913 state.overlays.clear_namespace(&ns, &mut state.marker_list);
1914 }
1915
1916 pub fn clear_search_highlights(&mut self) {
1919 self.clear_search_overlays();
1920 self.search_state = None;
1921 }
1922
1923 pub fn running_lsp_servers(&self) -> Vec<String> {
1926 self.lsp
1927 .as_ref()
1928 .map(|lsp| lsp.running_servers())
1929 .unwrap_or_default()
1930 }
1931
1932 pub fn pending_completion_requests_count(&self) -> usize {
1934 self.pending_completion_requests.len()
1935 }
1936
1937 pub fn completion_items_count(&self) -> usize {
1940 self.completion_items.as_ref().map_or(0, |v| v.len())
1941 }
1942
1943 pub fn initialized_lsp_server_count(&self, language: &str) -> usize {
1946 self.lsp
1947 .as_ref()
1948 .map(|lsp| {
1949 lsp.get_handles(language)
1950 .iter()
1951 .filter(|sh| sh.capabilities.initialized)
1952 .count()
1953 })
1954 .unwrap_or(0)
1955 }
1956
1957 pub fn shutdown_lsp_server(&mut self, language: &str) -> bool {
1961 self.lsp
1962 .as_mut()
1963 .map(|lsp| lsp.shutdown_server(language))
1964 .unwrap_or(false)
1965 }
1966
1967 pub fn enable_event_streaming<P: AsRef<std::path::Path>>(
1970 &mut self,
1971 path: P,
1972 ) -> anyhow::Result<()> {
1973 for event_log in self.event_logs.values_mut() {
1974 event_log.enable_streaming(&path)?;
1975 }
1976 Ok(())
1977 }
1978
1979 pub fn log_keystroke(&mut self, key_code: &str, modifiers: &str) {
1982 let buffer_id = self.active_buffer();
1983 if let Some(event_log) = self.event_logs.get_mut(&buffer_id) {
1984 event_log.log_keystroke(key_code, modifiers);
1985 }
1986 }
1987
1988 pub fn has_active_lsp_progress(&self) -> bool {
1991 !self.lsp_progress.is_empty()
1992 }
1993
1994 pub fn get_lsp_progress(&self) -> Vec<(String, String, Option<String>)> {
1997 self.lsp_progress
1998 .iter()
1999 .map(|(token, info)| (token.clone(), info.title.clone(), info.message.clone()))
2000 .collect()
2001 }
2002
2003 pub fn is_lsp_server_ready(&self, language: &str) -> bool {
2007 use crate::services::async_bridge::LspServerStatus;
2008 self.lsp_server_statuses
2009 .iter()
2010 .any(|((lang, server_name), status)| {
2011 if !matches!(status, LspServerStatus::Running) {
2012 return false;
2013 }
2014 if lang == language {
2015 return true;
2016 }
2017 self.lsp
2018 .as_ref()
2019 .and_then(|lsp| lsp.server_scope(server_name))
2020 .map(|scope| scope.accepts(language))
2021 .unwrap_or(false)
2022 })
2023 }
2024
2025 pub fn redirect_active_split_away_from_dock_if_needed(&mut self) {
2031 use crate::view::split::SplitRole;
2032 let Some((mgr, _)) = self.buffers.splits() else {
2033 return;
2034 };
2035 let active = mgr.active_split();
2036 if mgr.leaf_role(active) != Some(SplitRole::UtilityDock) {
2037 return;
2038 }
2039 let is_editor_leaf = |leaf| mgr.leaf_role(leaf) != Some(SplitRole::UtilityDock);
2040 let target = mgr.last_focused_where(is_editor_leaf).or_else(|| {
2041 mgr.root()
2042 .leaf_split_ids()
2043 .into_iter()
2044 .find(|leaf| is_editor_leaf(*leaf))
2045 });
2046 let Some(target) = target else {
2047 return;
2048 };
2049 if target == active {
2050 return;
2051 }
2052 self.split_manager_mut()
2053 .expect("active window must have a populated split layout")
2054 .set_active_split(target);
2055 }
2056
2057 pub fn restore_global_file_state(
2062 &mut self,
2063 buffer_id: BufferId,
2064 path: &std::path::Path,
2065 split_id: LeafId,
2066 ) {
2067 use crate::workspace::PersistedFileWorkspace;
2068
2069 let file_state = match PersistedFileWorkspace::load(path) {
2070 Some(state) => state,
2071 None => return,
2072 };
2073
2074 self.restore_buffer_state_in_split(buffer_id, split_id, &file_state);
2075 }
2076
2077 pub fn save_file_state_on_close(&self, buffer_id: BufferId) {
2082 use crate::workspace::{
2083 PersistedFileWorkspace, SerializedCursor, SerializedFileState, SerializedScroll,
2084 };
2085
2086 let abs_path = match self.buffer_metadata.get(&buffer_id) {
2087 Some(metadata) => match metadata.file_path() {
2088 Some(path) => path.to_path_buf(),
2089 None => return,
2090 },
2091 None => return,
2092 };
2093
2094 let view_state = self
2095 .buffers
2096 .splits()
2097 .expect("active window must have a populated split layout")
2098 .1
2099 .values()
2100 .find(|vs| vs.has_buffer(buffer_id));
2101
2102 let view_state = match view_state {
2103 Some(vs) => vs,
2104 None => return,
2105 };
2106
2107 let buf_state = match view_state.keyed_states.get(&buffer_id) {
2108 Some(bs) => bs,
2109 None => return,
2110 };
2111
2112 let primary_cursor = buf_state.cursors.primary();
2113 let file_state = SerializedFileState {
2114 cursor: SerializedCursor {
2115 position: primary_cursor.position,
2116 anchor: primary_cursor.anchor,
2117 sticky_column: primary_cursor.sticky_column,
2118 },
2119 additional_cursors: buf_state
2120 .cursors
2121 .iter()
2122 .skip(1)
2123 .map(|(_, cursor)| SerializedCursor {
2124 position: cursor.position,
2125 anchor: cursor.anchor,
2126 sticky_column: cursor.sticky_column,
2127 })
2128 .collect(),
2129 scroll: SerializedScroll {
2130 top_byte: buf_state.viewport.top_byte,
2131 top_view_line_offset: buf_state.viewport.top_view_line_offset,
2132 left_column: buf_state.viewport.left_column,
2133 },
2134 view_mode: Default::default(),
2135 compose_width: None,
2136 plugin_state: std::collections::HashMap::new(),
2137 folds: Vec::new(),
2138 };
2139
2140 PersistedFileWorkspace::save(&abs_path, file_state);
2141 tracing::debug!("Saved file state on close for {:?}", abs_path);
2142 }
2143
2144 pub(crate) fn take_pending_semantic_token_request(
2146 &mut self,
2147 request_id: u64,
2148 ) -> Option<crate::app::SemanticTokenFullRequest> {
2149 if let Some(request) = self.pending_semantic_token_requests.remove(&request_id) {
2150 self.semantic_tokens_in_flight.remove(&request.buffer_id);
2151 Some(request)
2152 } else {
2153 None
2154 }
2155 }
2156
2157 pub(crate) fn take_pending_semantic_token_range_request(
2159 &mut self,
2160 request_id: u64,
2161 ) -> Option<crate::app::SemanticTokenRangeRequest> {
2162 if let Some(request) = self
2163 .pending_semantic_token_range_requests
2164 .remove(&request_id)
2165 {
2166 self.semantic_tokens_range_in_flight
2167 .remove(&request.buffer_id);
2168 Some(request)
2169 } else {
2170 None
2171 }
2172 }
2173
2174 pub fn move_cursor_to_visible_area(&mut self, split_id: LeafId, buffer_id: BufferId) {
2177 let (top_byte, viewport_height) =
2178 if let Some(view_state) = self.buffers.splits().and_then(|(_, vs)| vs.get(&split_id)) {
2179 (
2180 view_state.viewport.top_byte,
2181 view_state.viewport.height as usize,
2182 )
2183 } else {
2184 return;
2185 };
2186
2187 if let Some(state) = self.buffers.get_mut(&buffer_id) {
2188 let buffer_len = state.buffer.len();
2189
2190 let mut iter = state.buffer.line_iterator(top_byte, 80);
2191 let mut bottom_byte = buffer_len;
2192
2193 for _ in 0..viewport_height {
2194 if let Some((pos, line)) = iter.next_line() {
2195 bottom_byte = pos + line.len();
2196 } else {
2197 bottom_byte = buffer_len;
2198 break;
2199 }
2200 }
2201
2202 if let Some(view_state) = self
2203 .split_view_states_mut()
2204 .and_then(|vs| vs.get_mut(&split_id))
2205 {
2206 let cursor_pos = view_state.cursors.primary().position;
2207 if cursor_pos < top_byte || cursor_pos > bottom_byte {
2208 let cursor = view_state.cursors.primary_mut();
2209 cursor.position = top_byte;
2210 }
2213 }
2214 }
2215 }
2216
2217 pub fn calculate_max_scroll_position(
2222 buffer: &mut crate::model::buffer::Buffer,
2223 viewport_height: usize,
2224 ) -> usize {
2225 if viewport_height == 0 {
2226 return 0;
2227 }
2228
2229 let buffer_len = buffer.len();
2230 if buffer_len == 0 {
2231 return 0;
2232 }
2233
2234 let mut line_count = 0;
2235 let mut iter = buffer.line_iterator(0, 80);
2236 while iter.next_line().is_some() {
2237 line_count += 1;
2238 }
2239
2240 if line_count <= viewport_height {
2241 return 0;
2242 }
2243
2244 let scrollable_lines = line_count.saturating_sub(viewport_height);
2245
2246 let mut iter = buffer.line_iterator(0, 80);
2247 let mut current_line = 0;
2248 let mut max_byte_pos = 0;
2249
2250 while current_line < scrollable_lines {
2251 if let Some((pos, _content)) = iter.next_line() {
2252 max_byte_pos = pos;
2253 current_line += 1;
2254 } else {
2255 break;
2256 }
2257 }
2258
2259 max_byte_pos
2260 }
2261
2262 pub fn split_at_position(&self, col: u16, row: u16) -> Option<(LeafId, BufferId)> {
2267 for &(split_id, buffer_id, content_rect, scrollbar_rect, _, _) in
2268 &self.layout_cache.split_areas
2269 {
2270 let in_content = col >= content_rect.x
2271 && col < content_rect.x + content_rect.width
2272 && row >= content_rect.y
2273 && row < content_rect.y + content_rect.height;
2274 let in_scrollbar = scrollbar_rect.width > 0
2275 && scrollbar_rect.height > 0
2276 && col >= scrollbar_rect.x
2277 && col < scrollbar_rect.x + scrollbar_rect.width
2278 && row >= scrollbar_rect.y
2279 && row < scrollbar_rect.y + scrollbar_rect.height;
2280 if in_content || in_scrollbar {
2281 return Some((split_id, buffer_id));
2282 }
2283 }
2284 None
2285 }
2286
2287 pub fn check_diagnostic_pull_timer(&mut self) -> bool {
2292 let Some((buffer_id, trigger_time)) = self.scheduled_diagnostic_pull else {
2293 return false;
2294 };
2295
2296 if std::time::Instant::now() < trigger_time {
2297 return false;
2298 }
2299
2300 self.scheduled_diagnostic_pull = None;
2301
2302 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
2303 return false;
2304 };
2305 let Some(uri) = metadata.file_uri().cloned() else {
2306 return false;
2307 };
2308 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
2309 return false;
2310 };
2311
2312 let previous_result_id = self.diagnostic_result_ids.get(uri.as_str()).cloned();
2313 let request_id = self.next_lsp_request_id;
2314 self.next_lsp_request_id += 1;
2315
2316 let Some(lsp) = self.lsp.as_mut() else {
2317 return false;
2318 };
2319 let Some(sh) = lsp.handle_for_feature_mut(&language, crate::types::LspFeature::Diagnostics)
2320 else {
2321 return false;
2322 };
2323 if let Err(e) =
2324 sh.handle
2325 .document_diagnostic(request_id, uri.as_uri().clone(), previous_result_id)
2326 {
2327 tracing::debug!(
2328 "Failed to pull diagnostics after edit for {}: {}",
2329 uri.as_str(),
2330 e
2331 );
2332 } else {
2333 tracing::debug!(
2334 "Pulling diagnostics after edit for {} (request_id={})",
2335 uri.as_str(),
2336 request_id
2337 );
2338 }
2339
2340 false
2341 }
2342
2343 pub fn open_local_file(&mut self, path: &std::path::Path) -> anyhow::Result<BufferId> {
2351 let resolved_path = if path.is_relative() {
2353 self.root.join(path)
2354 } else {
2355 path.to_path_buf()
2356 };
2357
2358 let display_path = resolved_path.clone();
2360
2361 let canonical_path = resolved_path
2363 .canonicalize()
2364 .unwrap_or_else(|_| resolved_path.clone());
2365 let path = canonical_path.as_path();
2366
2367 let already_open = self
2369 .buffers
2370 .iter()
2371 .find(|(_, state)| state.buffer.file_path() == Some(path))
2372 .map(|(id, _)| *id);
2373
2374 if let Some(id) = already_open {
2375 self.set_active_buffer(id);
2376 return Ok(id);
2377 }
2378
2379 let buffer_id = self.alloc_buffer_id();
2381
2382 let buffer = crate::model::buffer::Buffer::load_from_file(
2385 &canonical_path,
2386 self.config().editor.large_file_threshold_bytes as usize,
2387 std::sync::Arc::clone(&self.resources.local_filesystem),
2388 )?;
2389 let first_line = buffer.first_line_lossy();
2390 let detected =
2391 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
2392 &display_path,
2393 first_line.as_deref(),
2394 &self.resources.grammar_registry,
2395 &self.config().languages,
2396 self.config().default_language.as_deref(),
2397 );
2398 let state = crate::state::EditorState::from_buffer_with_language(buffer, detected);
2399
2400 self.buffers.insert(buffer_id, state);
2401 self.event_logs
2402 .insert(buffer_id, crate::model::event::EventLog::new());
2403
2404 let metadata = crate::app::types::BufferMetadata::with_file(
2406 path.to_path_buf(),
2407 &display_path,
2408 &self.root,
2409 self.authority().path_translation.as_ref(),
2410 );
2411 self.buffer_metadata.insert(buffer_id, metadata);
2412
2413 let target_split = self.preferred_split_for_file();
2415 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
2416 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
2417 let cfg = self.config().editor.clone();
2420 if let Some(view_state) = self
2421 .split_view_states_mut()
2422 .expect("active window must have a populated split layout")
2423 .get_mut(&target_split)
2424 {
2425 view_state.add_buffer(buffer_id);
2426 let buf_state = view_state.ensure_buffer_state(buffer_id);
2427 buf_state.apply_config_defaults(
2428 cfg.line_numbers,
2429 cfg.highlight_current_line,
2430 line_wrap,
2431 cfg.wrap_indent,
2432 wrap_column,
2433 cfg.rulers,
2434 );
2435 }
2436
2437 self.set_active_buffer(buffer_id);
2438
2439 let display_name = path.display().to_string();
2440 self.set_status_message(rust_i18n::t!("buffer.opened", name = display_name).to_string());
2441
2442 Ok(buffer_id)
2443 }
2444
2445 pub fn mark_buffer_read_only(&mut self, buffer_id: BufferId, read_only: bool) {
2449 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
2450 metadata.read_only = read_only;
2451 }
2452 if let Some(state) = self.buffers.get_mut(&buffer_id) {
2453 state.editing_disabled = read_only;
2454 }
2455 }
2456
2457 pub fn clear_warnings(&mut self) {
2460 self.warning_domains.general.clear();
2461 self.warning_domains.lsp.clear();
2462 self.set_status_message("Warnings cleared".to_string());
2463 }
2464
2465 pub fn update_lsp_warning_domain(&mut self) {
2469 let statuses = self.lsp_server_statuses.clone();
2471 self.warning_domains.lsp.update_from_statuses(&statuses);
2472 }
2473
2474 pub fn check_semantic_highlight_timer(&self) -> bool {
2479 self.buffers.any_needs_semantic_redraw()
2480 }
2481
2482 pub fn search_match_at_primary_cursor(&self) -> Option<std::ops::Range<usize>> {
2488 let search_state = self.search_state.as_ref()?;
2489 let pos = self.active_cursors().primary().position;
2490 let idx = match search_state.matches.binary_search(&pos) {
2491 Ok(i) => i,
2492 Err(0) => return None,
2493 Err(i) => i - 1,
2494 };
2495 let start = search_state.matches[idx];
2496 let len = *search_state.match_lengths.get(idx)?;
2497 if pos < start + len {
2498 Some(start..start + len)
2499 } else {
2500 None
2501 }
2502 }
2503
2504 pub fn update_search_highlights(
2508 &mut self,
2509 query: &str,
2510 search_fg: ratatui::style::Color,
2511 search_bg: ratatui::style::Color,
2512 ) {
2513 if query.is_empty() {
2514 self.clear_search_highlights();
2515 return;
2516 }
2517
2518 let case_sensitive = self.search_case_sensitive;
2519 let whole_word = self.search_whole_word;
2520 let use_regex = self.search_use_regex;
2521 let ns = self.search_namespace.clone();
2522
2523 let regex_pattern = if use_regex {
2524 if whole_word {
2525 format!(r"\b{}\b", query)
2526 } else {
2527 query.to_string()
2528 }
2529 } else {
2530 let escaped = regex::escape(query);
2531 if whole_word {
2532 format!(r"\b{}\b", escaped)
2533 } else {
2534 escaped
2535 }
2536 };
2537
2538 let regex = regex::RegexBuilder::new(®ex_pattern)
2539 .case_insensitive(!case_sensitive)
2540 .build();
2541 let regex = match regex {
2542 Ok(r) => r,
2543 Err(_) => {
2544 self.clear_search_highlights();
2545 return;
2546 }
2547 };
2548
2549 let active_split = self.effective_active_split();
2550 let (top_byte, visible_height) = self
2551 .buffers
2552 .splits()
2553 .expect("active window must have a populated split layout")
2554 .1
2555 .get(&active_split)
2556 .map(|vs| (vs.viewport.top_byte, vs.viewport.height.saturating_sub(2)))
2557 .unwrap_or((0, 20));
2558
2559 let state = self.active_state_mut();
2560 state.overlays.clear_namespace(&ns, &mut state.marker_list);
2561
2562 let visible_start = top_byte;
2563 let mut visible_end = top_byte;
2564 {
2565 let mut line_iter = state.buffer.line_iterator(top_byte, 80);
2566 for _ in 0..visible_height {
2567 if let Some((line_start, line_content)) = line_iter.next_line() {
2568 visible_end = line_start + line_content.len();
2569 } else {
2570 break;
2571 }
2572 }
2573 }
2574 visible_end = visible_end.min(state.buffer.len());
2575 let visible_text = state.get_text_range(visible_start, visible_end);
2576
2577 for mat in regex.find_iter(&visible_text) {
2578 let absolute_pos = visible_start + mat.start();
2579 let match_len = mat.end() - mat.start();
2580 let search_style = ratatui::style::Style::default().fg(search_fg).bg(search_bg);
2581 let overlay = crate::view::overlay::Overlay::with_namespace(
2582 &mut state.marker_list,
2583 absolute_pos..(absolute_pos + match_len),
2584 crate::view::overlay::OverlayFace::Style {
2585 style: search_style,
2586 },
2587 ns.clone(),
2588 )
2589 .with_priority_value(10);
2590 state.overlays.add(overlay);
2591 }
2592 }
2593
2594 pub fn file_explorer_is_visible(&self) -> bool {
2598 self.file_explorer_visible && self.file_explorer.is_some()
2599 }
2600
2601 pub fn file_explorer_extend_selection_up(&mut self) {
2603 if let Some(explorer) = self.file_explorer.as_mut() {
2604 explorer.extend_selection_up();
2605 }
2606 }
2607
2608 pub fn file_explorer_extend_selection_down(&mut self) {
2610 if let Some(explorer) = self.file_explorer.as_mut() {
2611 explorer.extend_selection_down();
2612 }
2613 }
2614
2615 pub fn file_explorer_toggle_select(&mut self) {
2617 if let Some(explorer) = self.file_explorer.as_mut() {
2618 explorer.toggle_select();
2619 }
2620 }
2621
2622 pub fn file_explorer_select_all(&mut self) {
2624 if let Some(explorer) = self.file_explorer.as_mut() {
2625 explorer.select_all();
2626 }
2627 }
2628
2629 pub fn file_explorer_search_push_char(&mut self, c: char) {
2631 if let Some(explorer) = self.file_explorer.as_mut() {
2632 explorer.search_push_char(c);
2633 explorer.update_scroll_for_selection();
2634 }
2635 }
2636
2637 pub fn file_explorer_search_pop_char(&mut self) {
2639 if let Some(explorer) = self.file_explorer.as_mut() {
2640 explorer.search_pop_char();
2641 explorer.update_scroll_for_selection();
2642 }
2643 }
2644
2645 pub fn schedule_folding_ranges_refresh(&mut self, buffer_id: BufferId) {
2651 const FOLDING_RANGES_DEBOUNCE_MS: u64 = 300;
2652 let next_time = std::time::Instant::now()
2653 + std::time::Duration::from_millis(FOLDING_RANGES_DEBOUNCE_MS);
2654 self.folding_ranges_debounce.insert(buffer_id, next_time);
2655 }
2656
2657 pub fn schedule_semantic_tokens_full_refresh(&mut self, buffer_id: BufferId) {
2661 const SEMANTIC_TOKENS_FULL_DEBOUNCE_MS: u64 = 500;
2662 if !self.resources.config.editor.enable_semantic_tokens_full {
2663 return;
2664 }
2665 let next_time = std::time::Instant::now()
2666 + std::time::Duration::from_millis(SEMANTIC_TOKENS_FULL_DEBOUNCE_MS);
2667 self.semantic_tokens_full_debounce
2668 .insert(buffer_id, next_time);
2669 }
2670
2671 pub(crate) fn send_lsp_changes_for_buffer(
2681 &mut self,
2682 buffer_id: BufferId,
2683 changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
2684 ) {
2685 const INLAY_HINTS_DEBOUNCE_MS: u64 = 500;
2686
2687 if changes.is_empty() {
2688 return;
2689 }
2690
2691 let metadata = match self.buffer_metadata.get(&buffer_id) {
2692 Some(m) => m,
2693 None => {
2694 tracing::debug!(
2695 "send_lsp_changes_for_buffer: no metadata for buffer {:?}",
2696 buffer_id
2697 );
2698 return;
2699 }
2700 };
2701
2702 if !metadata.lsp_enabled {
2703 tracing::debug!("send_lsp_changes_for_buffer: LSP disabled for this buffer");
2704 return;
2705 }
2706
2707 let uri = match metadata.file_uri() {
2708 Some(u) => u.clone(),
2709 None => {
2710 tracing::debug!(
2711 "send_lsp_changes_for_buffer: no URI for buffer (not a file or URI creation failed)"
2712 );
2713 return;
2714 }
2715 };
2716 let file_path = metadata.file_path().cloned();
2717
2718 let language = match self.buffers.get(&buffer_id).map(|s| s.language.clone()) {
2719 Some(l) => l,
2720 None => {
2721 tracing::debug!(
2722 "send_lsp_changes_for_buffer: no buffer state for {:?}",
2723 buffer_id
2724 );
2725 return;
2726 }
2727 };
2728
2729 tracing::trace!(
2730 "send_lsp_changes_for_buffer: sending {} changes to {} in single didChange notification",
2731 changes.len(),
2732 uri.as_str()
2733 );
2734
2735 use crate::services::lsp::manager::LspSpawnResult;
2736 let Some(lsp) = self.lsp.as_mut() else {
2737 tracing::debug!("send_lsp_changes_for_buffer: no LSP manager available");
2738 return;
2739 };
2740
2741 if lsp.try_spawn(&language, file_path.as_deref()) != LspSpawnResult::Spawned {
2742 tracing::debug!(
2743 "send_lsp_changes_for_buffer: LSP not running for {} (auto_start disabled)",
2744 language
2745 );
2746 return;
2747 }
2748
2749 let handles_needing_open: Vec<_> = {
2750 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
2751 return;
2752 };
2753 lsp.get_handles(&language)
2754 .into_iter()
2755 .filter(|sh| !metadata.lsp_opened_with.contains(&sh.handle.id()))
2756 .map(|sh| (sh.name.clone(), sh.handle.id()))
2757 .collect()
2758 };
2759
2760 if !handles_needing_open.is_empty() {
2761 let text = match self
2762 .buffers
2763 .get(&buffer_id)
2764 .and_then(|s| s.buffer.to_string())
2765 {
2766 Some(t) => t,
2767 None => {
2768 tracing::debug!(
2769 "send_lsp_changes_for_buffer: buffer text not available for didOpen"
2770 );
2771 return;
2772 }
2773 };
2774
2775 let Some(lsp) = self.lsp.as_mut() else {
2776 return;
2777 };
2778 for sh in lsp.get_handles_mut(&language) {
2779 if handles_needing_open
2780 .iter()
2781 .any(|(_, id)| *id == sh.handle.id())
2782 {
2783 if let Err(e) =
2784 sh.handle
2785 .did_open(uri.as_uri().clone(), text.clone(), language.clone())
2786 {
2787 tracing::warn!(
2788 "Failed to send didOpen to '{}' before didChange: {}",
2789 sh.name,
2790 e
2791 );
2792 } else {
2793 tracing::debug!(
2794 "Sent didOpen for {} to LSP handle '{}' before didChange",
2795 uri.as_str(),
2796 sh.name
2797 );
2798 }
2799 }
2800 }
2801
2802 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
2803 for (_, handle_id) in &handles_needing_open {
2804 metadata.lsp_opened_with.insert(*handle_id);
2805 }
2806 }
2807
2808 return;
2812 }
2813
2814 let Some(lsp) = self.lsp.as_mut() else {
2815 return;
2816 };
2817 let mut any_sent = false;
2818 for sh in lsp.get_handles_mut(&language) {
2819 if let Err(e) = sh.handle.did_change(uri.as_uri().clone(), changes.clone()) {
2820 tracing::warn!("Failed to send didChange to '{}': {}", sh.name, e);
2821 } else {
2822 any_sent = true;
2823 }
2824 }
2825 if any_sent {
2826 tracing::trace!("Successfully sent batched didChange to LSP");
2827
2828 if let Some(state) = self.buffers.get(&buffer_id) {
2829 if let Some(path) = state.buffer.file_path() {
2830 crate::services::lsp::diagnostics::invalidate_cache_for_file(
2831 &path.to_string_lossy(),
2832 );
2833 }
2834 }
2835
2836 self.scheduled_diagnostic_pull = Some((
2837 buffer_id,
2838 std::time::Instant::now() + std::time::Duration::from_millis(1000),
2839 ));
2840
2841 if self.resources.config.editor.enable_inlay_hints {
2842 self.scheduled_inlay_hints_request = Some((
2843 buffer_id,
2844 std::time::Instant::now()
2845 + std::time::Duration::from_millis(INLAY_HINTS_DEBOUNCE_MS),
2846 ));
2847 }
2848 }
2849 }
2850
2851 pub fn invalidate_layouts_for_buffer(&mut self, buffer_id: BufferId) {
2855 let Some((mgr, vs_map)) = self.buffers.splits_mut() else {
2856 return;
2857 };
2858 let splits_for_buffer = mgr.splits_for_buffer(buffer_id);
2859 for split_id in splits_for_buffer {
2860 if let Some(view_state) = vs_map.get_mut(&split_id) {
2861 view_state.invalidate_layout();
2862 view_state.view_transform = None;
2863 view_state.view_transform_stale = true;
2864 }
2865 }
2866 }
2867
2868 pub fn adjust_other_split_cursors_for_event(&mut self, event: &Event) {
2875 let current_buffer_id = self.active_buffer();
2876 let buffer_len = self
2877 .buffers
2878 .get(¤t_buffer_id)
2879 .map(|s| s.buffer.len())
2880 .unwrap_or(0);
2881 let Some((mgr, vs_map)) = self.buffers.splits_mut() else {
2882 return;
2883 };
2884 let current_split_id = mgr.active_split();
2885 let splits_for_buffer = mgr.splits_for_buffer(current_buffer_id);
2886
2887 if let Event::BulkEdit { new_cursors, .. } = event {
2888 for split_id in splits_for_buffer {
2889 if split_id == current_split_id {
2890 continue;
2891 }
2892 if let Some(view_state) = vs_map.get_mut(&split_id) {
2893 if let Some((_, pos, _)) = new_cursors.first() {
2894 let new_pos = (*pos).min(buffer_len);
2895 view_state.cursors.primary_mut().position = new_pos;
2896 view_state.cursors.primary_mut().anchor = None;
2897 }
2898 }
2899 }
2900 return;
2901 }
2902
2903 let adjustments: Vec<(usize, usize, usize)> = match event {
2904 Event::Insert { position, text, .. } => {
2905 vec![(*position, 0, text.len())]
2906 }
2907 Event::Delete { range, .. } => {
2908 vec![(range.start, range.len(), 0)]
2909 }
2910 Event::Batch { events, .. } => events
2911 .iter()
2912 .filter_map(|e| match e {
2913 Event::Insert { position, text, .. } => Some((*position, 0, text.len())),
2914 Event::Delete { range, .. } => Some((range.start, range.len(), 0)),
2915 _ => None,
2916 })
2917 .collect(),
2918 _ => Vec::new(),
2919 };
2920
2921 if adjustments.is_empty() {
2922 return;
2923 }
2924
2925 for split_id in splits_for_buffer {
2926 if split_id == current_split_id {
2927 continue;
2928 }
2929 if let Some(view_state) = vs_map.get_mut(&split_id) {
2930 for (edit_pos, old_len, new_len) in &adjustments {
2931 view_state
2932 .cursors
2933 .adjust_for_edit(*edit_pos, *old_len, *new_len);
2934 }
2935 }
2936 }
2937 }
2938
2939 pub(crate) fn handle_scroll_event(&mut self, line_offset: isize) {
2945 use crate::view::ui::view_pipeline::ViewLineIterator;
2946
2947 let Some((mgr, _)) = self.buffers.splits() else {
2948 return;
2949 };
2950 let active_split = mgr.active_split();
2951
2952 if let Some(group) = self
2953 .scroll_sync_manager
2954 .find_group_for_split(active_split.into())
2955 {
2956 let left = group.left_split;
2957 let right = group.right_split;
2958 if let Some(vs_map) = self.split_view_states_mut() {
2959 if let Some(vs) = vs_map.get_mut(&LeafId(left)) {
2960 vs.viewport.set_skip_ensure_visible();
2961 }
2962 if let Some(vs) = vs_map.get_mut(&LeafId(right)) {
2963 vs.viewport.set_skip_ensure_visible();
2964 }
2965 }
2966 }
2967
2968 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
2969 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
2970 let splits_to_scroll = if let Some(group_id) = sync_group {
2971 mgr.get_splits_in_group(group_id, vs_map)
2972 } else {
2973 vec![active_split]
2974 };
2975
2976 let tab_size = self.resources.config.editor.tab_size;
2977 for split_id in splits_to_scroll {
2978 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
2979 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
2980 continue;
2981 };
2982
2983 let view_transform_tokens = vs_map
2984 .get(&split_id)
2985 .and_then(|vs| vs.view_transform.as_ref())
2986 .map(|vt| vt.tokens.clone());
2987
2988 self.buffers
2989 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
2990 let soft_breaks = state.collect_soft_break_positions();
2991 let virtual_lines = state.collect_virtual_line_positions();
2992 let buffer = &mut state.buffer;
2993 if let Some(tokens) = view_transform_tokens {
2994 let view_lines: Vec<_> =
2995 ViewLineIterator::new(&tokens, false, false, tab_size, false).collect();
2996 view_state
2997 .viewport
2998 .scroll_view_lines(&view_lines, line_offset);
2999 } else if line_offset > 0 {
3000 view_state.viewport.scroll_down(
3001 buffer,
3002 &soft_breaks,
3003 &virtual_lines,
3004 line_offset as usize,
3005 );
3006 } else {
3007 view_state.viewport.scroll_up(
3008 buffer,
3009 &soft_breaks,
3010 &virtual_lines,
3011 line_offset.unsigned_abs(),
3012 );
3013 }
3014 view_state.viewport.set_skip_ensure_visible();
3015 });
3016 }
3017 }
3018
3019 pub(crate) fn handle_set_viewport_event(&mut self, top_line: usize) {
3021 let Some((mgr, _)) = self.buffers.splits() else {
3022 return;
3023 };
3024 let active_split = mgr.active_split();
3025
3026 if self
3027 .scroll_sync_manager
3028 .is_split_synced(active_split.into())
3029 {
3030 if let Some(group) = self
3031 .scroll_sync_manager
3032 .find_group_for_split_mut(active_split.into())
3033 {
3034 let scroll_line = if group.is_left_split(active_split.into()) {
3035 top_line
3036 } else {
3037 group.right_to_left_line(top_line)
3038 };
3039 group.set_scroll_line(scroll_line);
3040 }
3041
3042 let (left, right) = match self
3043 .scroll_sync_manager
3044 .find_group_for_split(active_split.into())
3045 {
3046 Some(group) => (group.left_split, group.right_split),
3047 None => return,
3048 };
3049 if let Some(vs_map) = self.split_view_states_mut() {
3050 if let Some(vs) = vs_map.get_mut(&LeafId(left)) {
3051 vs.viewport.set_skip_ensure_visible();
3052 }
3053 if let Some(vs) = vs_map.get_mut(&LeafId(right)) {
3054 vs.viewport.set_skip_ensure_visible();
3055 }
3056 }
3057 return;
3058 }
3059
3060 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
3061 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
3062 let splits_to_scroll = if let Some(group_id) = sync_group {
3063 mgr.get_splits_in_group(group_id, vs_map)
3064 } else {
3065 vec![active_split]
3066 };
3067
3068 for split_id in splits_to_scroll {
3069 let (mgr, _) = self.buffers.splits().expect("splits checked above");
3070 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
3071 continue;
3072 };
3073
3074 self.buffers
3075 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
3076 view_state.viewport.scroll_to(&mut state.buffer, top_line);
3077 view_state.viewport.set_skip_ensure_visible();
3078 });
3079 }
3080 }
3081
3082 pub(crate) fn handle_recenter_event(&mut self) {
3084 let Some((mgr, vs_map)) = self.buffers.splits() else {
3085 return;
3086 };
3087 let active_split = mgr.active_split();
3088
3089 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
3090 let splits_to_recenter = if let Some(group_id) = sync_group {
3091 mgr.get_splits_in_group(group_id, vs_map)
3092 } else {
3093 vec![active_split]
3094 };
3095
3096 for split_id in splits_to_recenter {
3097 let (mgr, _) = self.buffers.splits().expect("splits checked above");
3098 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
3099 continue;
3100 };
3101
3102 self.buffers
3103 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
3104 let buffer = &mut state.buffer;
3105 let cursor = *view_state.cursors.primary();
3106 let viewport_height = view_state.viewport.visible_line_count();
3107 let target_rows_from_top = viewport_height / 2;
3108
3109 let mut iter = buffer.line_iterator(cursor.position, 80);
3110 for _ in 0..target_rows_from_top {
3111 if iter.prev().is_none() {
3112 break;
3113 }
3114 }
3115 let new_top_byte = iter.current_position();
3116 view_state.viewport.top_byte = new_top_byte;
3117 view_state.viewport.set_skip_ensure_visible();
3118 });
3119 }
3120 }
3121
3122 pub fn set_pane_buffer(&mut self, leaf: LeafId, buffer_id: BufferId) {
3135 let (mgr, vs_map) = self
3136 .buffers
3137 .splits_mut()
3138 .expect("active window must have a populated split layout");
3139 mgr.set_split_buffer(leaf, buffer_id);
3140 if let Some(view_state) = vs_map.get_mut(&leaf) {
3141 view_state.switch_buffer(buffer_id);
3142 }
3143 }
3144}
3145
3146