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>>,
291
292 pub(crate) layout_cache: WindowLayoutCache,
301
302 pub(crate) chrome_layout: ChromeLayout,
308
309 pub(crate) terminal_width: u16,
315 pub(crate) terminal_height: u16,
316
317 pub(crate) resources: WindowResources,
322
323 pub preview: Option<(LeafId, BufferId)>,
334
335 pub terminal_mode: bool,
339
340 pub terminal_mode_resume: std::collections::HashSet<BufferId>,
344
345 pub seen_byte_ranges: HashMap<BufferId, std::collections::HashSet<(usize, usize)>>,
349
350 pub previous_viewports: HashMap<LeafId, (usize, u16, u16)>,
355
356 pub same_buffer_scroll_sync: bool,
359
360 pub(crate) interactive_replace_state: Option<crate::app::types::InteractiveReplaceState>,
365
366 pub scroll_sync_manager: crate::view::scroll_sync::ScrollSyncManager,
369
370 pub file_explorer_visible: bool,
372
373 pub file_explorer_sync_in_progress: bool,
375
376 pub file_explorer_width: crate::config::ExplorerWidth,
378
379 pub file_explorer_side: crate::config::FileExplorerSide,
381
382 pub pending_file_explorer_show_hidden: Option<bool>,
385 pub pending_file_explorer_show_gitignored: Option<bool>,
386
387 pub file_explorer_decorations:
390 HashMap<String, Vec<crate::view::file_tree::FileExplorerDecoration>>,
391
392 pub file_explorer_decoration_cache: crate::view::file_tree::FileExplorerDecorationCache,
395
396 pub(crate) hover: crate::app::hover::HoverState,
400
401 pub(crate) search_state: Option<crate::app::types::SearchState>,
403
404 pub search_namespace: crate::view::overlay::OverlayNamespace,
407
408 pub pending_search_range: Option<std::ops::Range<usize>>,
411
412 pub live_grep_last_state: Option<crate::services::live_grep_state::LiveGrepLastState>,
415
416 pub overlay_preview_state: Option<crate::app::types::OverlayPreviewState>,
419
420 pub auto_revert_enabled: bool,
423
424 pub file_rapid_change_counts: HashMap<PathBuf, (std::time::Instant, u32)>,
427
428 pub(crate) goto_line_preview: Option<crate::app::GotoLinePreviewSnapshot>,
431
432 pub pending_async_prompt_callback: Option<fresh_core::api::JsCallbackId>,
435
436 pub pending_quit_unnamed_save: Vec<BufferId>,
439
440 pub search_case_sensitive: bool,
443 pub search_whole_word: bool,
444 pub search_use_regex: bool,
445 pub search_confirm_each: bool,
446
447 pub scheduled_diagnostic_pull: Option<(BufferId, std::time::Instant)>,
451 pub scheduled_inlay_hints_request: Option<(BufferId, std::time::Instant)>,
452
453 pub user_dismissed_lsp_languages: std::collections::HashSet<String>,
457
458 pub editor_mode: Option<String>,
462
463 pub prompt_histories: HashMap<String, crate::input::input_history::InputHistory>,
467
468 pub pending_close_buffer: Option<BufferId>,
471
472 pub completion_service: crate::services::completion::CompletionService,
477
478 pub lsp_diagnostic_namespace: crate::view::overlay::OverlayNamespace,
482
483 pub diagnostic_result_ids: HashMap<String, String>,
487
488 pub(crate) lsp_progress: HashMap<String, crate::app::LspProgressInfo>,
492
493 pub lsp_server_statuses:
496 HashMap<(String, String), crate::services::async_bridge::LspServerStatus>,
497
498 pub lsp_menu_contributions: HashMap<(String, String), Vec<crate::app::LspMenuItem>>,
513
514 pub(crate) lsp_window_messages: Vec<crate::app::LspMessageEntry>,
518
519 pub(crate) lsp_log_messages: Vec<crate::app::LspMessageEntry>,
522
523 pub stored_push_diagnostics: HashMap<String, HashMap<String, Vec<lsp_types::Diagnostic>>>,
528
529 pub stored_pull_diagnostics: HashMap<String, Vec<lsp_types::Diagnostic>>,
534
535 pub stored_diagnostics: Arc<HashMap<String, Vec<lsp_types::Diagnostic>>>,
539
540 pub stored_folding_ranges: Arc<HashMap<String, Vec<lsp_types::FoldingRange>>>,
544
545 pub dir_mod_times: HashMap<PathBuf, std::time::SystemTime>,
549
550 pub last_auto_revert_poll: std::time::Instant,
552
553 pub last_file_tree_poll: std::time::Instant,
555
556 pub git_index_resolved: bool,
559
560 #[allow(clippy::type_complexity)]
563 pub pending_file_poll_rx:
564 Option<std::sync::mpsc::Receiver<Vec<(PathBuf, Option<std::time::SystemTime>)>>>,
565
566 #[allow(clippy::type_complexity)]
568 pub pending_dir_poll_rx: Option<
569 std::sync::mpsc::Receiver<(
570 Vec<(
571 crate::view::file_tree::NodeId,
572 PathBuf,
573 Option<std::time::SystemTime>,
574 )>,
575 Option<(PathBuf, std::time::SystemTime)>,
576 )>,
577 >,
578
579 pub ephemeral_terminals: std::collections::HashSet<crate::services::terminal::TerminalId>,
583
584 pub plugin_dev_workspaces:
588 HashMap<BufferId, crate::services::plugins::plugin_dev_workspace::PluginDevWorkspace>,
589
590 pub status_bar_values: HashMap<BufferId, HashMap<String, String>>,
596
597 pub(crate) mouse_state: crate::app::types::MouseState,
600
601 pub key_context: crate::input::keybindings::KeyContext,
606
607 pub chord_state: Vec<(crossterm::event::KeyCode, crossterm::event::KeyModifiers)>,
610
611 pub previous_click_time: Option<std::time::Instant>,
614 pub previous_click_position: Option<(u16, u16)>,
615 pub click_count: u8,
616
617 pub mouse_enabled: bool,
619
620 pub mouse_cursor_position: Option<(u16, u16)>,
623 pub gpm_active: bool,
624
625 pub menu_bar_visible: bool,
628 pub menu_bar_auto_shown: bool,
629 pub tab_bar_visible: bool,
630 pub status_bar_visible: bool,
631 pub prompt_line_visible: bool,
632
633 pub last_auto_recovery_save: std::time::Instant,
636 pub last_persistent_auto_save: std::time::Instant,
637
638 pub warning_domains: crate::app::warning_domains::WarningDomainRegistry,
640
641 pub tab_context_menu: Option<crate::app::types::TabContextMenu>,
643
644 pub file_explorer_context_menu: Option<crate::app::types::FileExplorerContextMenu>,
646
647 pub theme_info_popup: Option<crate::app::types::ThemeInfoPopup>,
649
650 pub event_debug: Option<crate::app::event_debug::EventDebug>,
654
655 pub file_open_state: Option<crate::app::file_open::FileOpenState>,
658
659 pub file_browser_layout: Option<crate::view::ui::FileBrowserLayout>,
661
662 pub buffer_groups: HashMap<crate::app::types::BufferGroupId, crate::app::types::BufferGroup>,
664 pub buffer_to_group: HashMap<BufferId, crate::app::types::BufferGroupId>,
666 pub next_buffer_group_id: usize,
668
669 pub pending_next_key_callbacks: std::collections::VecDeque<fresh_core::api::JsCallbackId>,
671
672 pub key_capture_active: bool,
674
675 pub pending_key_capture_buffer: std::collections::VecDeque<fresh_core::api::KeyEventPayload>,
678
679 pub(crate) macros: crate::app::macros::MacroState,
682
683 pub active_custom_contexts: std::collections::HashSet<String>,
686
687 pub keyboard_capture: bool,
690
691 pub review_hunks: Vec<fresh_core::api::ReviewHunk>,
693
694 pub pending_file_opens: Vec<crate::app::PendingFileOpen>,
696
697 pub pending_hot_exit_recovery: bool,
699
700 pub wait_tracking: HashMap<BufferId, (u64, bool)>,
702
703 pub completed_waits: Vec<u64>,
705
706 pub(crate) line_scan: crate::app::line_scan::LineScan,
709
710 pub(crate) search_scan: crate::app::search_scan::SearchScan,
712
713 pub search_overlay_top_byte: Option<usize>,
715
716 pub animations: crate::view::animation::AnimationRunner,
718
719 pub plugin_errors: Vec<String>,
722
723 pub file_explorer_clipboard: Option<crate::app::file_explorer::FileExplorerClipboard>,
727
728 pub process_groups: ProcessGroups,
736}
737
738impl Window {
739 pub fn apply_folding_ranges_response(
744 &mut self,
745 buffer_id: BufferId,
746 lsp_ranges: Vec<lsp_types::FoldingRange>,
747 ) {
748 let Some(state) = self.buffers.get_mut(&buffer_id) else {
749 return;
750 };
751 state
752 .folding_ranges
753 .set_from_lsp(&state.buffer, &mut state.marker_list, lsp_ranges);
754 }
755
756 pub fn alloc_lsp_request_id(&mut self) -> u64 {
760 let id = self.next_lsp_request_id;
761 self.next_lsp_request_id += 1;
762 id
763 }
764
765 pub fn has_pending_lsp_requests(&self) -> bool {
768 !self.pending_completion_requests.is_empty()
769 || self.pending_goto_definition_request.is_some()
770 }
771
772 pub(crate) fn cancel_pending_lsp_requests(&mut self) {
778 self.scheduled_completion_trigger = None;
779 if !self.pending_completion_requests.is_empty() {
780 let ids: Vec<u64> = self.pending_completion_requests.drain().collect();
781 for request_id in ids {
782 tracing::debug!("Canceling pending LSP completion request {}", request_id);
783 self.send_lsp_cancel_request(request_id);
784 }
785 }
786 if let Some(request_id) = self.pending_goto_definition_request.take() {
787 tracing::debug!(
788 "Canceling pending LSP goto-definition request {}",
789 request_id
790 );
791 self.send_lsp_cancel_request(request_id);
792 }
793 }
794
795 pub(crate) fn send_lsp_cancel_request(&mut self, request_id: u64) {
799 let buffer_id = self.active_buffer();
800 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
801 return;
802 };
803 if let Some(lsp) = self.lsp.as_mut() {
804 if let Some(handle) = lsp.get_handle_mut(&language) {
805 if let Err(e) = handle.cancel_request(request_id) {
806 tracing::warn!("Failed to send LSP cancel request: {}", e);
807 } else {
808 tracing::debug!("Sent $/cancelRequest for request_id={}", request_id);
809 }
810 }
811 }
812 }
813
814 pub fn toggle_tab_bar(&mut self) {
816 self.tab_bar_visible = !self.tab_bar_visible;
817 let key = if self.tab_bar_visible {
818 "toggle.tab_bar_shown"
819 } else {
820 "toggle.tab_bar_hidden"
821 };
822 self.set_status_message(rust_i18n::t!(key).to_string());
823 }
824
825 pub fn toggle_status_bar(&mut self) {
827 self.status_bar_visible = !self.status_bar_visible;
828 let key = if self.status_bar_visible {
829 "toggle.status_bar_shown"
830 } else {
831 "toggle.status_bar_hidden"
832 };
833 self.set_status_message(rust_i18n::t!(key).to_string());
834 }
835
836 pub fn toggle_prompt_line(&mut self) {
838 self.prompt_line_visible = !self.prompt_line_visible;
839 let key = if self.prompt_line_visible {
840 "toggle.prompt_line_shown"
841 } else {
842 "toggle.prompt_line_hidden"
843 };
844 self.set_status_message(rust_i18n::t!(key).to_string());
845 }
846
847 pub fn toggle_scroll_sync(&mut self) {
850 self.same_buffer_scroll_sync = !self.same_buffer_scroll_sync;
851 let key = if self.same_buffer_scroll_sync {
852 "toggle.scroll_sync_enabled"
853 } else {
854 "toggle.scroll_sync_disabled"
855 };
856 self.set_status_message(rust_i18n::t!(key).to_string());
857 }
858
859 pub fn toggle_debug_highlights(&mut self) {
863 let buffer_id = self.active_buffer();
864 if let Some(state) = self.buffers.get_mut(&buffer_id) {
865 state.debug_highlight_mode = !state.debug_highlight_mode;
866 let key = if state.debug_highlight_mode {
867 "toggle.debug_mode_on"
868 } else {
869 "toggle.debug_mode_off"
870 };
871 self.set_status_message(rust_i18n::t!(key).to_string());
872 }
873 }
874
875 pub(crate) fn build_search_regex(&self, query: &str) -> Result<regex::Regex, String> {
880 crate::app::regex_replace::build_search_regex(
881 query,
882 self.search_use_regex,
883 self.search_whole_word,
884 self.search_case_sensitive,
885 )
886 }
887
888 pub fn is_editing_disabled(&self) -> bool {
891 self.active_state().editing_disabled
892 }
893
894 pub(super) fn update_modified_from_event_log(&mut self) {
898 let buffer_id = self.active_buffer();
899 let is_at_saved = self
900 .event_logs
901 .get(&buffer_id)
902 .map(|log| log.is_at_saved_position())
903 .unwrap_or(false);
904 if let Some(state) = self.buffers.get_mut(&buffer_id) {
905 state.buffer.set_modified(!is_at_saved);
906 }
907 }
908
909 pub fn is_lsp_language_user_dismissed(&self, language: &str) -> bool {
912 self.user_dismissed_lsp_languages.contains(language)
913 }
914
915 pub fn dismiss_lsp_language(&mut self, language: &str) {
918 self.user_dismissed_lsp_languages
919 .insert(language.to_string());
920 }
921
922 pub fn undismiss_lsp_language(&mut self, language: &str) {
925 self.user_dismissed_lsp_languages.remove(language);
926 }
927
928 pub(crate) fn server_supports_code_action_resolve(&self) -> bool {
931 let Some(language) = self
932 .buffers
933 .get(&self.active_buffer())
934 .map(|s| s.language.clone())
935 else {
936 return false;
937 };
938 if let Some(lsp) = self.lsp.as_ref() {
939 for sh in lsp.get_handles(&language) {
940 if sh.capabilities.code_action_resolve {
941 return true;
942 }
943 }
944 }
945 false
946 }
947
948 pub(crate) fn server_supports_completion_resolve(&self) -> bool {
951 let Some(language) = self
952 .buffers
953 .get(&self.active_buffer())
954 .map(|s| s.language.clone())
955 else {
956 return false;
957 };
958 if let Some(lsp) = self.lsp.as_ref() {
959 for sh in lsp.get_handles(&language) {
960 if sh.capabilities.completion_resolve {
961 return true;
962 }
963 }
964 }
965 false
966 }
967
968 pub(crate) fn server_supports_prepare_rename(&self) -> bool {
973 let Some(language) = self
974 .buffers
975 .get(&self.active_buffer())
976 .map(|s| s.language.clone())
977 else {
978 return false;
979 };
980 if let Some(lsp) = self.lsp.as_ref() {
981 for sh in lsp.get_handles(&language) {
982 if sh.capabilities.rename {
983 return true;
984 }
985 }
986 }
987 false
988 }
989
990 pub(crate) fn send_prepare_rename(&mut self) {
995 let cursor_pos = self.active_cursors().primary().position;
996 let (line, character) = self
997 .active_state()
998 .buffer
999 .position_to_lsp_position(cursor_pos);
1000
1001 let buffer_id = self.active_buffer();
1002 let metadata = match self.buffer_metadata.get(&buffer_id) {
1003 Some(m) if m.lsp_enabled => m,
1004 _ => return,
1005 };
1006 let uri = match metadata.file_uri() {
1007 Some(u) => u.clone(),
1008 None => return,
1009 };
1010 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
1011 return;
1012 };
1013
1014 let request_id = self.alloc_lsp_request_id();
1015
1016 if let Some(lsp) = self.lsp.as_mut() {
1017 if let Some(sh) = lsp.handle_for_feature_mut(&language, LspFeature::Rename) {
1018 if let Err(e) = sh.handle.prepare_rename(
1019 request_id,
1020 uri.as_uri().clone(),
1021 line as u32,
1022 character as u32,
1023 ) {
1024 tracing::warn!("Failed to send prepareRename: {}", e);
1025 }
1026 }
1027 }
1028 }
1029
1030 pub(crate) fn send_completion_resolve(&mut self, item: lsp_types::CompletionItem) {
1035 let Some(language) = self
1036 .buffers
1037 .get(&self.active_buffer())
1038 .map(|s| s.language.clone())
1039 else {
1040 return;
1041 };
1042 let request_id = self.alloc_lsp_request_id();
1043 if let Some(lsp) = self.lsp.as_mut() {
1044 for sh in lsp.get_handles_mut(&language) {
1045 if sh.capabilities.completion_resolve {
1046 if let Err(e) = sh.handle.completion_resolve(request_id, item.clone()) {
1047 tracing::warn!(
1048 "Failed to send completionItem/resolve to '{}': {}",
1049 sh.name,
1050 e
1051 );
1052 }
1053 return;
1054 }
1055 }
1056 }
1057 }
1058
1059 pub fn apply_event_to_buffer(
1065 &mut self,
1066 buffer_id: BufferId,
1067 split_id: LeafId,
1068 event: &crate::model::event::Event,
1069 ) {
1070 self.buffers
1071 .with_buffer_and_split(buffer_id, split_id, |state, vs| {
1072 state.apply(&mut vs.cursors, event);
1073 });
1074 }
1075
1076 pub fn apply_event_to_keyed_buffer(
1082 &mut self,
1083 buffer_id: BufferId,
1084 split_id: LeafId,
1085 event: &crate::model::event::Event,
1086 ) {
1087 self.buffers
1088 .with_buffer_and_split(buffer_id, split_id, |state, vs| {
1089 if let Some(keyed) = vs.keyed_states.get_mut(&buffer_id) {
1090 state.apply(&mut keyed.cursors, event);
1091 }
1092 });
1093 }
1094
1095 pub fn ensure_cursor_visible_for_split(&mut self, buffer_id: BufferId, split_id: LeafId) {
1100 self.buffers
1101 .with_buffer_and_split(buffer_id, split_id, |state, vs| {
1102 vs.ensure_cursor_visible(&mut state.buffer, &state.marker_list);
1103 });
1104 }
1105
1106 pub fn scroll_split_viewport_to(
1113 &mut self,
1114 buffer_id: BufferId,
1115 split_id: LeafId,
1116 target_line: usize,
1117 lock_against_ensure_visible: bool,
1118 ) {
1119 self.buffers
1120 .with_buffer_and_split(buffer_id, split_id, |state, vs| {
1121 vs.viewport.scroll_to(&mut state.buffer, target_line);
1122 if lock_against_ensure_visible {
1123 vs.viewport.set_skip_ensure_visible();
1124 }
1125 });
1126 }
1127
1128 pub fn add_fold(
1133 &mut self,
1134 buffer_id: BufferId,
1135 start: usize,
1136 end: usize,
1137 placeholder: Option<String>,
1138 ) -> bool {
1139 self.buffers
1140 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1141 for vs in vs_map.values_mut() {
1142 if vs.keyed_states.contains_key(&buffer_id) {
1143 let buf_state = vs.ensure_buffer_state(buffer_id);
1144 buf_state.folds.add(
1145 &mut state.marker_list,
1146 start,
1147 end,
1148 placeholder.clone(),
1149 );
1150 }
1151 }
1152 })
1153 .is_some()
1154 }
1155
1156 pub fn clear_folds(&mut self, buffer_id: BufferId) -> bool {
1159 self.buffers
1160 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1161 for vs in vs_map.values_mut() {
1162 if vs.keyed_states.contains_key(&buffer_id) {
1163 let buf_state = vs.ensure_buffer_state(buffer_id);
1164 buf_state.folds.clear(&mut state.marker_list);
1165 }
1166 }
1167 })
1168 .is_some()
1169 }
1170
1171 pub fn set_buffer_cursor_in_splits(
1178 &mut self,
1179 buffer_id: BufferId,
1180 position: usize,
1181 splits: &[LeafId],
1182 ) {
1183 self.buffers
1184 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1185 for leaf_id in splits {
1186 let Some(view_state) = vs_map.get_mut(leaf_id) else {
1187 continue;
1188 };
1189 view_state.cursors.primary_mut().move_to(position, false);
1190 view_state.ensure_cursor_visible(&mut state.buffer, &state.marker_list);
1191 }
1192 });
1193 }
1194
1195 pub fn set_split_scroll_to_byte(
1201 &mut self,
1202 buffer_id: BufferId,
1203 leaf_id: LeafId,
1204 top_byte: usize,
1205 ) {
1206 self.buffers
1207 .with_buffer_and_split(buffer_id, leaf_id, |state, view_state| {
1208 let total_bytes = state.buffer.len();
1209 let clamped_byte = top_byte.min(total_bytes);
1210 let target_line = state
1211 .buffer
1212 .offset_to_position(clamped_byte)
1213 .map(|p| p.line)
1214 .unwrap_or(0);
1215 view_state
1216 .viewport
1217 .scroll_to(&mut state.buffer, target_line);
1218 view_state.viewport.top_byte = clamped_byte;
1219 view_state.viewport.top_view_line_offset = 0;
1220 view_state.viewport.set_skip_ensure_visible();
1221 });
1222 }
1223
1224 pub fn scroll_buffer_to_line_in_splits(
1230 &mut self,
1231 buffer_id: BufferId,
1232 target_leaves: &[LeafId],
1233 line: usize,
1234 ) {
1235 self.buffers
1236 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1237 for leaf_id in target_leaves {
1238 let Some(view_state) = vs_map.get_mut(leaf_id) else {
1239 continue;
1240 };
1241 let viewport_height = view_state.viewport.height as usize;
1242 let lines_above = viewport_height / 3;
1243 let target = line.saturating_sub(lines_above);
1244 view_state.viewport.scroll_to(&mut state.buffer, target);
1245 view_state.viewport.set_skip_ensure_visible();
1246 }
1247 });
1248 }
1249
1250 pub fn restore_buffer_state_in_split(
1260 &mut self,
1261 buffer_id: BufferId,
1262 split_id: LeafId,
1263 file_state: &crate::workspace::SerializedFileState,
1264 ) {
1265 self.buffers
1266 .with_buffer_and_split(buffer_id, split_id, |buffer_state, vs| {
1267 let Some(buf_state) = vs.keyed_states.get_mut(&buffer_id) else {
1268 return;
1269 };
1270 let max_pos = buffer_state.buffer.len();
1271 let cursor_pos = file_state.cursor.position.min(max_pos);
1272 buf_state.cursors.primary_mut().position = cursor_pos;
1273 buf_state.cursors.primary_mut().anchor =
1274 file_state.cursor.anchor.map(|a| a.min(max_pos));
1275 buf_state.viewport.top_byte = file_state.scroll.top_byte;
1276 buf_state.viewport.left_column = file_state.scroll.left_column;
1277 crate::app::navigation::reconcile_restored_buffer_view(
1278 buf_state,
1279 &mut buffer_state.buffer,
1280 );
1281 });
1282 }
1283
1284 pub fn enter_terminal_scrollback_view(&mut self, buffer_id: BufferId, leaf_id: LeafId) {
1290 self.buffers
1291 .with_buffer_and_split(buffer_id, leaf_id, |state, view_state| {
1292 view_state.viewport.line_wrap_enabled = false;
1293 view_state.viewport.clear_skip_ensure_visible();
1294 view_state.ensure_cursor_visible(&mut state.buffer, &state.marker_list);
1295 });
1296 }
1297
1298 pub fn install_terminal_buffer_state(
1306 &mut self,
1307 buffer_id: BufferId,
1308 new_state: crate::state::EditorState,
1309 ) {
1310 self.buffers
1311 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1312 *state = new_state;
1313 let total = state.buffer.total_bytes();
1314 for vs in vs_map.values_mut() {
1315 if vs.has_buffer(buffer_id) {
1316 vs.cursors.primary_mut().position = total;
1317 let buf_state = vs.ensure_buffer_state(buffer_id);
1326 buf_state.show_line_numbers = false;
1327 buf_state.highlight_current_line = false;
1328 buf_state.viewport.line_wrap_enabled = false;
1329 }
1330 }
1331 state.buffer.set_modified(false);
1332 state.editing_disabled = true;
1333 state.margins.configure_for_line_numbers(false);
1334 });
1335 }
1336
1337 pub fn scroll_split_by_lines(
1345 &mut self,
1346 buffer_id: BufferId,
1347 leaf_id: LeafId,
1348 delta: i32,
1349 view_transform_tokens: Option<Vec<fresh_core::api::ViewTokenWire>>,
1350 tab_size: usize,
1351 ) {
1352 self.buffers
1353 .with_buffer_and_split(buffer_id, leaf_id, |state, view_state| {
1354 let soft_breaks = state.collect_soft_break_positions();
1355 let virtual_lines = state.collect_virtual_line_positions();
1356 let buffer = &mut state.buffer;
1357 let top_byte_before = view_state.viewport.top_byte;
1358 if let Some(tokens) = view_transform_tokens {
1359 use crate::view::ui::view_pipeline::ViewLineIterator;
1360 let view_lines: Vec<_> =
1361 ViewLineIterator::new(&tokens, false, false, tab_size, false).collect();
1362 view_state
1363 .viewport
1364 .scroll_view_lines(&view_lines, delta as isize);
1365 } else if delta < 0 {
1366 let lines_to_scroll = delta.unsigned_abs() as usize;
1367 view_state.viewport.scroll_up(
1368 buffer,
1369 &soft_breaks,
1370 &virtual_lines,
1371 lines_to_scroll,
1372 );
1373 } else {
1374 let lines_to_scroll = delta as usize;
1375 view_state.viewport.scroll_down(
1376 buffer,
1377 &soft_breaks,
1378 &virtual_lines,
1379 lines_to_scroll,
1380 );
1381 }
1382 view_state.viewport.set_skip_ensure_visible();
1383
1384 if let Some(folds) = view_state.keyed_states.get(&buffer_id).map(|bs| &bs.folds) {
1385 if !folds.is_empty() {
1386 let top_line = buffer.get_line_number(view_state.viewport.top_byte);
1387 if let Some(range) = folds
1388 .resolved_ranges(buffer, &state.marker_list)
1389 .iter()
1390 .find(|r| top_line >= r.start_line && top_line <= r.end_line)
1391 {
1392 let target_line = if delta >= 0 {
1393 range.end_line.saturating_add(1)
1394 } else {
1395 range.header_line
1396 };
1397 let target_byte = buffer
1398 .line_start_offset(target_line)
1399 .unwrap_or_else(|| buffer.len());
1400 view_state.viewport.top_byte = target_byte;
1401 view_state.viewport.top_view_line_offset = 0;
1402 }
1403 }
1404 }
1405 tracing::trace!(
1406 "scroll_split_by_lines: delta={}, top_byte {} -> {}",
1407 delta,
1408 top_byte_before,
1409 view_state.viewport.top_byte
1410 );
1411 });
1412 }
1413
1414 pub fn clear_lsp_overlays_for_buffer(
1418 &mut self,
1419 buffer_id: BufferId,
1420 diagnostic_namespace: &crate::model::event::OverlayNamespace,
1421 ) {
1422 self.buffers
1423 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1424 state
1425 .overlays
1426 .clear_namespace(diagnostic_namespace, &mut state.marker_list);
1427 state.virtual_texts.clear(&mut state.marker_list);
1428 state.folding_ranges.clear(&mut state.marker_list);
1429 for view_state in vs_map.values_mut() {
1430 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
1431 buf_state.folds.clear(&mut state.marker_list);
1432 }
1433 }
1434 });
1435 }
1436
1437 pub fn split_manager_mut(&mut self) -> Option<&mut SplitManager> {
1443 self.buffers.split_manager_mut()
1444 }
1445
1446 pub fn split_view_states_mut(&mut self) -> Option<&mut HashMap<LeafId, SplitViewState>> {
1448 self.buffers.split_view_states_mut()
1449 }
1450
1451 pub fn splits_mut(
1454 &mut self,
1455 ) -> Option<(&mut SplitManager, &mut HashMap<LeafId, SplitViewState>)> {
1456 self.buffers.splits_mut().map(|(m, vs)| (m, vs))
1457 }
1458
1459 pub fn new(
1467 id: WindowId,
1468 label: impl Into<String>,
1469 root: PathBuf,
1470 resources: WindowResources,
1471 ) -> Self {
1472 let mut label = label.into();
1473 if label.is_empty() {
1474 label = root
1475 .file_name()
1476 .and_then(|n| n.to_str())
1477 .map(str::to_owned)
1478 .unwrap_or_else(|| "main".to_owned());
1479 }
1480 let now = resources.time_source.now();
1487 Self {
1488 id,
1489 label,
1490 root,
1491 file_explorer: None,
1492 file_mod_times: HashMap::new(),
1493 plugin_state: HashMap::new(),
1494 lsp: None,
1495 panel_ids: HashMap::new(),
1496 buffers: WindowBuffers::new(),
1497 buffer_metadata: HashMap::new(),
1498 terminal_manager: crate::services::terminal::TerminalManager::new(),
1499 terminal_buffers: HashMap::new(),
1500 terminal_backing_files: HashMap::new(),
1501 terminal_log_files: HashMap::new(),
1502 event_logs: HashMap::new(),
1503 status_message: None,
1504 plugin_status_message: None,
1505 prompt: None,
1506 bridge: crate::services::async_bridge::AsyncBridge::new(),
1507 next_lsp_request_id: 0,
1508 pending_completion_requests: std::collections::HashSet::new(),
1509 completion_items: None,
1510 scheduled_completion_trigger: None,
1511 dabbrev_state: None,
1512 pending_goto_definition_request: None,
1513 pending_references_request: None,
1514 pending_references_symbol: String::new(),
1515 pending_signature_help_request: None,
1516 pending_code_actions_requests: std::collections::HashSet::new(),
1517 pending_code_actions_server_names: std::collections::HashMap::new(),
1518 pending_code_actions: None,
1519 pending_inlay_hints_requests: std::collections::HashMap::new(),
1520 pending_folding_range_requests: std::collections::HashMap::new(),
1521 folding_ranges_in_flight: std::collections::HashMap::new(),
1522 folding_ranges_debounce: std::collections::HashMap::new(),
1523 pending_semantic_token_requests: std::collections::HashMap::new(),
1524 semantic_tokens_in_flight: std::collections::HashMap::new(),
1525 semantic_tokens_full_debounce: std::collections::HashMap::new(),
1526 pending_semantic_token_range_requests: std::collections::HashMap::new(),
1527 semantic_tokens_range_in_flight: std::collections::HashMap::new(),
1528 semantic_tokens_range_last_request: std::collections::HashMap::new(),
1529 semantic_tokens_range_applied: std::collections::HashMap::new(),
1530 position_history: crate::input::position_history::PositionHistory::new(),
1531 in_navigation: false,
1532 suppress_position_history_once: false,
1533 bookmarks: crate::app::bookmarks::BookmarkState::default(),
1534 grouped_subtrees: HashMap::new(),
1535 composite_buffers: HashMap::new(),
1536 composite_view_states: HashMap::new(),
1537 layout_cache: WindowLayoutCache::default(),
1538 chrome_layout: ChromeLayout::default(),
1539 terminal_width: 80,
1540 terminal_height: 24,
1541 preview: None,
1542 terminal_mode: false,
1543 terminal_mode_resume: std::collections::HashSet::new(),
1544 seen_byte_ranges: HashMap::new(),
1545 previous_viewports: HashMap::new(),
1546 same_buffer_scroll_sync: false,
1547 interactive_replace_state: None,
1548 scroll_sync_manager: crate::view::scroll_sync::ScrollSyncManager::new(),
1549 file_explorer_visible: false,
1550 file_explorer_sync_in_progress: false,
1551 file_explorer_width: resources.config.file_explorer.width,
1552 file_explorer_side: resources.config.file_explorer.side,
1553 pending_file_explorer_show_hidden: None,
1554 pending_file_explorer_show_gitignored: None,
1555 file_explorer_decorations: HashMap::new(),
1556 file_explorer_decoration_cache:
1557 crate::view::file_tree::FileExplorerDecorationCache::default(),
1558 hover: crate::app::hover::HoverState::default(),
1559 search_state: None,
1560 search_namespace: crate::view::overlay::OverlayNamespace::from_string(
1561 "search".to_string(),
1562 ),
1563 pending_search_range: None,
1564 live_grep_last_state: None,
1565 overlay_preview_state: None,
1566 auto_revert_enabled: true,
1567 file_rapid_change_counts: HashMap::new(),
1568 goto_line_preview: None,
1569 pending_async_prompt_callback: None,
1570 pending_quit_unnamed_save: Vec::new(),
1571 search_case_sensitive: true,
1572 search_whole_word: false,
1573 search_use_regex: false,
1574 search_confirm_each: false,
1575 scheduled_diagnostic_pull: None,
1576 scheduled_inlay_hints_request: None,
1577 user_dismissed_lsp_languages: std::collections::HashSet::new(),
1578 editor_mode: None,
1579 prompt_histories: HashMap::new(),
1580 pending_close_buffer: None,
1581 completion_service: crate::services::completion::CompletionService::new(),
1582 lsp_diagnostic_namespace: crate::view::overlay::OverlayNamespace::from_string(
1583 "lsp-diagnostic".to_string(),
1584 ),
1585 diagnostic_result_ids: HashMap::new(),
1586 lsp_progress: HashMap::new(),
1587 lsp_server_statuses: HashMap::new(),
1588 lsp_menu_contributions: HashMap::new(),
1589 lsp_window_messages: Vec::new(),
1590 lsp_log_messages: Vec::new(),
1591 stored_push_diagnostics: HashMap::new(),
1592 stored_pull_diagnostics: HashMap::new(),
1593 stored_diagnostics: Arc::new(HashMap::new()),
1594 stored_folding_ranges: Arc::new(HashMap::new()),
1595 dir_mod_times: HashMap::new(),
1596 last_auto_revert_poll: now,
1597 last_file_tree_poll: now,
1598 git_index_resolved: false,
1599 pending_file_poll_rx: None,
1600 pending_dir_poll_rx: None,
1601 ephemeral_terminals: std::collections::HashSet::new(),
1602 plugin_dev_workspaces: HashMap::new(),
1603 status_bar_values: HashMap::new(),
1604 mouse_state: crate::app::types::MouseState::default(),
1605 key_context: crate::input::keybindings::KeyContext::Normal,
1606 chord_state: Vec::new(),
1607 previous_click_time: None,
1608 previous_click_position: None,
1609 click_count: 0,
1610 mouse_enabled: false,
1611 mouse_cursor_position: None,
1612 gpm_active: false,
1613 menu_bar_visible: resources.config.editor.show_menu_bar,
1614 menu_bar_auto_shown: false,
1615 tab_bar_visible: resources.config.editor.show_tab_bar,
1616 status_bar_visible: resources.config.editor.show_status_bar,
1617 prompt_line_visible: resources.config.editor.show_prompt_line,
1618 last_auto_recovery_save: now,
1619 last_persistent_auto_save: now,
1620 warning_domains: crate::app::warning_domains::WarningDomainRegistry::default(),
1621 tab_context_menu: None,
1622 file_explorer_context_menu: None,
1623 theme_info_popup: None,
1624 event_debug: None,
1625 file_open_state: None,
1626 file_browser_layout: None,
1627 buffer_groups: HashMap::new(),
1628 buffer_to_group: HashMap::new(),
1629 next_buffer_group_id: 0,
1630 pending_next_key_callbacks: std::collections::VecDeque::new(),
1631 key_capture_active: false,
1632 pending_key_capture_buffer: std::collections::VecDeque::new(),
1633 macros: crate::app::macros::MacroState::default(),
1634 active_custom_contexts: std::collections::HashSet::new(),
1635 keyboard_capture: false,
1636 review_hunks: Vec::new(),
1637 pending_file_opens: Vec::new(),
1638 pending_hot_exit_recovery: false,
1639 wait_tracking: HashMap::new(),
1640 completed_waits: Vec::new(),
1641 line_scan: crate::app::line_scan::LineScan::default(),
1642 search_scan: crate::app::search_scan::SearchScan::default(),
1643 search_overlay_top_byte: None,
1644 animations: crate::view::animation::AnimationRunner::default(),
1645 plugin_errors: Vec::new(),
1646 file_explorer_clipboard: None,
1647 process_groups: ProcessGroups::default(),
1648 resources,
1649 }
1650 }
1651
1652 pub fn config(&self) -> &crate::config::Config {
1662 &self.resources.config
1663 }
1664
1665 pub fn authority(&self) -> &crate::services::authority::Authority {
1667 &self.resources.authority
1668 }
1669
1670 pub fn alloc_buffer_id(&self) -> BufferId {
1672 self.resources.buffer_id_alloc.next()
1673 }
1674
1675 pub fn set_status_message(&mut self, message: String) {
1680 tracing::info!(target: "status", "{}", message);
1681 self.plugin_status_message = None;
1682 self.status_message = Some(message);
1683 }
1684
1685 pub fn clear_status_message(&mut self) {
1687 self.status_message = None;
1688 }
1689
1690 pub fn effective_active_pair(&self) -> (LeafId, BufferId) {
1700 let (mgr, vs_map) = self
1701 .buffers
1702 .splits()
1703 .expect("active window must have a populated split layout");
1704 let active_split = mgr.active_split();
1705 if let Some(vs) = vs_map.get(&active_split) {
1706 if vs.active_group_tab.is_some() {
1707 if let Some(inner_leaf) = vs.focused_group_leaf {
1708 if let Some(inner_vs) = vs_map.get(&inner_leaf) {
1709 let inner_buf = inner_vs.active_buffer;
1710 if self.buffers.get(&inner_buf).is_some()
1711 && inner_vs.keyed_states.contains_key(&inner_buf)
1712 {
1713 return (inner_leaf, inner_buf);
1714 }
1715 }
1716 }
1717 }
1718 }
1719 let outer_buf = mgr
1720 .active_buffer_id()
1721 .expect("Editor always has at least one buffer");
1722 if self.buffers.get(&outer_buf).is_some() {
1735 (active_split, outer_buf)
1736 } else if let Some(any) = self.buffers.find_id(|_, _| true) {
1737 tracing::warn!(
1738 stale_buffer_id = ?outer_buf,
1739 fallback_buffer_id = ?any,
1740 active_split = ?active_split,
1741 "effective_active_pair: split manager's active leaf points at \
1742 a BufferId missing from window.buffers (issue #1939). Falling \
1743 back to any live buffer; the split tree is in an inconsistent \
1744 state and should be repaired"
1745 );
1746 (active_split, any)
1747 } else {
1748 tracing::error!(
1752 stale_buffer_id = ?outer_buf,
1753 active_split = ?active_split,
1754 "effective_active_pair: window.buffers is empty AND the split \
1755 manager has a stale active buffer — no recovery possible, \
1756 next render will panic"
1757 );
1758 (active_split, outer_buf)
1759 }
1760 }
1761
1762 #[inline]
1764 pub fn active_buffer(&self) -> BufferId {
1765 let (_, buf) = self.effective_active_pair();
1766 buf
1767 }
1768
1769 pub fn effective_tabs_width(&self) -> u16 {
1773 if self.file_explorer_visible && self.file_explorer.is_some() {
1774 let explorer = self.file_explorer_width.to_cols(self.terminal_width);
1775 self.terminal_width.saturating_sub(explorer)
1776 } else {
1777 self.terminal_width
1778 }
1779 }
1780
1781 #[inline]
1784 pub fn effective_active_split(&self) -> LeafId {
1785 let (split, _) = self.effective_active_pair();
1786 split
1787 }
1788
1789 pub fn active_state(&self) -> &crate::state::EditorState {
1793 let buf = self.active_buffer();
1794 self.buffers
1795 .get(&buf)
1796 .expect("active buffer must be present in window")
1797 }
1798
1799 pub fn active_state_mut(&mut self) -> &mut crate::state::EditorState {
1801 let buf = self.active_buffer();
1802 self.buffers
1803 .get_mut(&buf)
1804 .expect("active buffer must be present in window")
1805 }
1806
1807 pub fn active_cursors(&self) -> &crate::model::cursor::Cursors {
1811 let split_id = self.effective_active_split();
1812 &self
1813 .buffers
1814 .splits()
1815 .expect("active window must have a populated split layout")
1816 .1
1817 .get(&split_id)
1818 .expect("active split must be in view-state map")
1819 .cursors
1820 }
1821
1822 pub fn active_cursors_mut(&mut self) -> &mut crate::model::cursor::Cursors {
1824 let split_id = self.effective_active_split();
1825 &mut self
1826 .buffers
1827 .splits_mut()
1828 .expect("active window must have a populated split layout")
1829 .1
1830 .get_mut(&split_id)
1831 .expect("active split must be in view-state map")
1832 .cursors
1833 }
1834
1835 pub fn active_event_log(&self) -> &crate::model::event::EventLog {
1837 let buf = self.active_buffer();
1838 self.event_logs
1839 .get(&buf)
1840 .expect("active buffer must have an event log")
1841 }
1842
1843 pub fn active_event_log_mut(&mut self) -> &mut crate::model::event::EventLog {
1845 let buf = self.active_buffer();
1846 self.event_logs
1847 .get_mut(&buf)
1848 .expect("active buffer must have an event log")
1849 }
1850
1851 pub fn promote_buffer_from_preview(&mut self, buffer_id: BufferId) {
1856 if let Some(m) = self.buffer_metadata.get_mut(&buffer_id) {
1857 m.is_preview = false;
1858 }
1859 if let Some((_, id)) = self.preview {
1860 if id == buffer_id {
1861 self.preview = None;
1862 }
1863 }
1864 }
1865
1866 pub fn promote_active_buffer_from_preview(&mut self) {
1869 let id = self.active_buffer();
1870 self.promote_buffer_from_preview(id);
1871 }
1872
1873 pub fn promote_current_preview(&mut self) {
1878 if let Some((_, id)) = self.preview.take() {
1879 if let Some(m) = self.buffer_metadata.get_mut(&id) {
1880 m.is_preview = false;
1881 }
1882 }
1883 }
1884
1885 pub fn promote_preview_if_not_in_split(&mut self, new_split: LeafId) {
1889 if let Some((preview_split, _)) = self.preview {
1890 if preview_split != new_split {
1891 self.promote_current_preview();
1892 }
1893 }
1894 }
1895
1896 pub fn is_buffer_preview(&self, buffer_id: BufferId) -> bool {
1901 self.buffer_metadata
1902 .get(&buffer_id)
1903 .map(|m| m.is_preview)
1904 .unwrap_or(false)
1905 }
1906
1907 pub fn current_preview(&self) -> Option<(LeafId, BufferId)> {
1910 self.preview
1911 }
1912
1913 pub fn is_terminal_buffer(&self, buffer_id: BufferId) -> bool {
1917 self.terminal_buffers.contains_key(&buffer_id)
1918 }
1919
1920 pub fn get_terminal_id(
1923 &self,
1924 buffer_id: BufferId,
1925 ) -> Option<crate::services::terminal::TerminalId> {
1926 self.terminal_buffers.get(&buffer_id).copied()
1927 }
1928
1929 pub fn clear_search_overlays(&mut self) {
1932 let ns = self.search_namespace.clone();
1933 let state = self.active_state_mut();
1934 state.overlays.clear_namespace(&ns, &mut state.marker_list);
1935 }
1936
1937 pub fn clear_search_highlights(&mut self) {
1940 self.clear_search_overlays();
1941 self.search_state = None;
1942 }
1943
1944 pub fn running_lsp_servers(&self) -> Vec<String> {
1947 self.lsp
1948 .as_ref()
1949 .map(|lsp| lsp.running_servers())
1950 .unwrap_or_default()
1951 }
1952
1953 pub fn pending_completion_requests_count(&self) -> usize {
1955 self.pending_completion_requests.len()
1956 }
1957
1958 pub fn completion_items_count(&self) -> usize {
1961 self.completion_items.as_ref().map_or(0, |v| v.len())
1962 }
1963
1964 pub fn initialized_lsp_server_count(&self, language: &str) -> usize {
1967 self.lsp
1968 .as_ref()
1969 .map(|lsp| {
1970 lsp.get_handles(language)
1971 .iter()
1972 .filter(|sh| sh.capabilities.initialized)
1973 .count()
1974 })
1975 .unwrap_or(0)
1976 }
1977
1978 pub fn shutdown_lsp_server(&mut self, language: &str) -> bool {
1982 self.lsp
1983 .as_mut()
1984 .map(|lsp| lsp.shutdown_server(language))
1985 .unwrap_or(false)
1986 }
1987
1988 pub fn enable_event_streaming<P: AsRef<std::path::Path>>(
1991 &mut self,
1992 path: P,
1993 ) -> anyhow::Result<()> {
1994 for event_log in self.event_logs.values_mut() {
1995 event_log.enable_streaming(&path)?;
1996 }
1997 Ok(())
1998 }
1999
2000 pub fn log_keystroke(&mut self, key_code: &str, modifiers: &str) {
2003 let buffer_id = self.active_buffer();
2004 if let Some(event_log) = self.event_logs.get_mut(&buffer_id) {
2005 event_log.log_keystroke(key_code, modifiers);
2006 }
2007 }
2008
2009 pub fn has_active_lsp_progress(&self) -> bool {
2012 !self.lsp_progress.is_empty()
2013 }
2014
2015 pub fn get_lsp_progress(&self) -> Vec<(String, String, Option<String>)> {
2018 self.lsp_progress
2019 .iter()
2020 .map(|(token, info)| (token.clone(), info.title.clone(), info.message.clone()))
2021 .collect()
2022 }
2023
2024 pub fn is_lsp_server_ready(&self, language: &str) -> bool {
2028 use crate::services::async_bridge::LspServerStatus;
2029 self.lsp_server_statuses
2030 .iter()
2031 .any(|((lang, server_name), status)| {
2032 if !matches!(status, LspServerStatus::Running) {
2033 return false;
2034 }
2035 if lang == language {
2036 return true;
2037 }
2038 self.lsp
2039 .as_ref()
2040 .and_then(|lsp| lsp.server_scope(server_name))
2041 .map(|scope| scope.accepts(language))
2042 .unwrap_or(false)
2043 })
2044 }
2045
2046 pub fn redirect_active_split_away_from_dock_if_needed(&mut self) {
2052 use crate::view::split::SplitRole;
2053 let Some((mgr, _)) = self.buffers.splits() else {
2054 return;
2055 };
2056 let active = mgr.active_split();
2057 if mgr.leaf_role(active) != Some(SplitRole::UtilityDock) {
2058 return;
2059 }
2060 let is_editor_leaf = |leaf| mgr.leaf_role(leaf) != Some(SplitRole::UtilityDock);
2061 let target = mgr.last_focused_where(is_editor_leaf).or_else(|| {
2062 mgr.root()
2063 .leaf_split_ids()
2064 .into_iter()
2065 .find(|leaf| is_editor_leaf(*leaf))
2066 });
2067 let Some(target) = target else {
2068 return;
2069 };
2070 if target == active {
2071 return;
2072 }
2073 self.split_manager_mut()
2074 .expect("active window must have a populated split layout")
2075 .set_active_split(target);
2076 }
2077
2078 pub fn restore_global_file_state(
2083 &mut self,
2084 buffer_id: BufferId,
2085 path: &std::path::Path,
2086 split_id: LeafId,
2087 ) {
2088 use crate::workspace::PersistedFileWorkspace;
2089
2090 let file_state = match PersistedFileWorkspace::load(path) {
2091 Some(state) => state,
2092 None => return,
2093 };
2094
2095 self.restore_buffer_state_in_split(buffer_id, split_id, &file_state);
2096 }
2097
2098 pub fn save_file_state_on_close(&self, buffer_id: BufferId) {
2103 use crate::workspace::{
2104 PersistedFileWorkspace, SerializedCursor, SerializedFileState, SerializedScroll,
2105 };
2106
2107 let abs_path = match self.buffer_metadata.get(&buffer_id) {
2108 Some(metadata) => match metadata.file_path() {
2109 Some(path) => path.to_path_buf(),
2110 None => return,
2111 },
2112 None => return,
2113 };
2114
2115 let view_state = self
2116 .buffers
2117 .splits()
2118 .expect("active window must have a populated split layout")
2119 .1
2120 .values()
2121 .find(|vs| vs.has_buffer(buffer_id));
2122
2123 let view_state = match view_state {
2124 Some(vs) => vs,
2125 None => return,
2126 };
2127
2128 let buf_state = match view_state.keyed_states.get(&buffer_id) {
2129 Some(bs) => bs,
2130 None => return,
2131 };
2132
2133 let primary_cursor = buf_state.cursors.primary();
2134 let file_state = SerializedFileState {
2135 cursor: SerializedCursor {
2136 position: primary_cursor.position,
2137 anchor: primary_cursor.anchor,
2138 sticky_column: primary_cursor.sticky_column,
2139 },
2140 additional_cursors: buf_state
2141 .cursors
2142 .iter()
2143 .skip(1)
2144 .map(|(_, cursor)| SerializedCursor {
2145 position: cursor.position,
2146 anchor: cursor.anchor,
2147 sticky_column: cursor.sticky_column,
2148 })
2149 .collect(),
2150 scroll: SerializedScroll {
2151 top_byte: buf_state.viewport.top_byte,
2152 top_view_line_offset: buf_state.viewport.top_view_line_offset,
2153 left_column: buf_state.viewport.left_column,
2154 },
2155 view_mode: Default::default(),
2156 compose_width: None,
2157 plugin_state: std::collections::HashMap::new(),
2158 folds: Vec::new(),
2159 };
2160
2161 PersistedFileWorkspace::save(&abs_path, file_state);
2162 tracing::debug!("Saved file state on close for {:?}", abs_path);
2163 }
2164
2165 pub(crate) fn take_pending_semantic_token_request(
2167 &mut self,
2168 request_id: u64,
2169 ) -> Option<crate::app::SemanticTokenFullRequest> {
2170 if let Some(request) = self.pending_semantic_token_requests.remove(&request_id) {
2171 self.semantic_tokens_in_flight.remove(&request.buffer_id);
2172 Some(request)
2173 } else {
2174 None
2175 }
2176 }
2177
2178 pub(crate) fn take_pending_semantic_token_range_request(
2180 &mut self,
2181 request_id: u64,
2182 ) -> Option<crate::app::SemanticTokenRangeRequest> {
2183 if let Some(request) = self
2184 .pending_semantic_token_range_requests
2185 .remove(&request_id)
2186 {
2187 self.semantic_tokens_range_in_flight
2188 .remove(&request.buffer_id);
2189 Some(request)
2190 } else {
2191 None
2192 }
2193 }
2194
2195 pub fn move_cursor_to_visible_area(&mut self, split_id: LeafId, buffer_id: BufferId) {
2198 let (top_byte, viewport_height) =
2199 if let Some(view_state) = self.buffers.splits().and_then(|(_, vs)| vs.get(&split_id)) {
2200 (
2201 view_state.viewport.top_byte,
2202 view_state.viewport.height as usize,
2203 )
2204 } else {
2205 return;
2206 };
2207
2208 if let Some(state) = self.buffers.get_mut(&buffer_id) {
2209 let buffer_len = state.buffer.len();
2210
2211 let mut iter = state.buffer.line_iterator(top_byte, 80);
2212 let mut bottom_byte = buffer_len;
2213
2214 for _ in 0..viewport_height {
2215 if let Some((pos, line)) = iter.next_line() {
2216 bottom_byte = pos + line.len();
2217 } else {
2218 bottom_byte = buffer_len;
2219 break;
2220 }
2221 }
2222
2223 if let Some(view_state) = self
2224 .split_view_states_mut()
2225 .and_then(|vs| vs.get_mut(&split_id))
2226 {
2227 let cursor_pos = view_state.cursors.primary().position;
2228 if cursor_pos < top_byte || cursor_pos > bottom_byte {
2229 let cursor = view_state.cursors.primary_mut();
2230 cursor.position = top_byte;
2231 }
2234 }
2235 }
2236 }
2237
2238 pub fn calculate_max_scroll_position(
2243 buffer: &mut crate::model::buffer::Buffer,
2244 viewport_height: usize,
2245 ) -> usize {
2246 if viewport_height == 0 {
2247 return 0;
2248 }
2249
2250 let buffer_len = buffer.len();
2251 if buffer_len == 0 {
2252 return 0;
2253 }
2254
2255 let mut line_count = 0;
2256 let mut iter = buffer.line_iterator(0, 80);
2257 while iter.next_line().is_some() {
2258 line_count += 1;
2259 }
2260
2261 if line_count <= viewport_height {
2262 return 0;
2263 }
2264
2265 let scrollable_lines = line_count.saturating_sub(viewport_height);
2266
2267 let mut iter = buffer.line_iterator(0, 80);
2268 let mut current_line = 0;
2269 let mut max_byte_pos = 0;
2270
2271 while current_line < scrollable_lines {
2272 if let Some((pos, _content)) = iter.next_line() {
2273 max_byte_pos = pos;
2274 current_line += 1;
2275 } else {
2276 break;
2277 }
2278 }
2279
2280 max_byte_pos
2281 }
2282
2283 pub fn split_at_position(&self, col: u16, row: u16) -> Option<(LeafId, BufferId)> {
2288 for &(split_id, buffer_id, content_rect, scrollbar_rect, _, _) in
2289 &self.layout_cache.split_areas
2290 {
2291 let in_content = col >= content_rect.x
2292 && col < content_rect.x + content_rect.width
2293 && row >= content_rect.y
2294 && row < content_rect.y + content_rect.height;
2295 let in_scrollbar = scrollbar_rect.width > 0
2296 && scrollbar_rect.height > 0
2297 && col >= scrollbar_rect.x
2298 && col < scrollbar_rect.x + scrollbar_rect.width
2299 && row >= scrollbar_rect.y
2300 && row < scrollbar_rect.y + scrollbar_rect.height;
2301 if in_content || in_scrollbar {
2302 return Some((split_id, buffer_id));
2303 }
2304 }
2305 None
2306 }
2307
2308 pub fn check_diagnostic_pull_timer(&mut self) -> bool {
2313 let Some((buffer_id, trigger_time)) = self.scheduled_diagnostic_pull else {
2314 return false;
2315 };
2316
2317 if std::time::Instant::now() < trigger_time {
2318 return false;
2319 }
2320
2321 self.scheduled_diagnostic_pull = None;
2322
2323 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
2324 return false;
2325 };
2326 let Some(uri) = metadata.file_uri().cloned() else {
2327 return false;
2328 };
2329 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
2330 return false;
2331 };
2332
2333 let previous_result_id = self.diagnostic_result_ids.get(uri.as_str()).cloned();
2334 let request_id = self.next_lsp_request_id;
2335 self.next_lsp_request_id += 1;
2336
2337 let Some(lsp) = self.lsp.as_mut() else {
2338 return false;
2339 };
2340 let Some(sh) = lsp.handle_for_feature_mut(&language, crate::types::LspFeature::Diagnostics)
2341 else {
2342 return false;
2343 };
2344 if let Err(e) =
2345 sh.handle
2346 .document_diagnostic(request_id, uri.as_uri().clone(), previous_result_id)
2347 {
2348 tracing::debug!(
2349 "Failed to pull diagnostics after edit for {}: {}",
2350 uri.as_str(),
2351 e
2352 );
2353 } else {
2354 tracing::debug!(
2355 "Pulling diagnostics after edit for {} (request_id={})",
2356 uri.as_str(),
2357 request_id
2358 );
2359 }
2360
2361 false
2362 }
2363
2364 pub fn open_local_file(&mut self, path: &std::path::Path) -> anyhow::Result<BufferId> {
2372 let resolved_path = if path.is_relative() {
2374 self.root.join(path)
2375 } else {
2376 path.to_path_buf()
2377 };
2378
2379 let display_path = resolved_path.clone();
2381
2382 let canonical_path = resolved_path
2384 .canonicalize()
2385 .unwrap_or_else(|_| resolved_path.clone());
2386 let path = canonical_path.as_path();
2387
2388 let already_open = self
2390 .buffers
2391 .iter()
2392 .find(|(_, state)| state.buffer.file_path() == Some(path))
2393 .map(|(id, _)| *id);
2394
2395 if let Some(id) = already_open {
2396 self.set_active_buffer(id);
2397 return Ok(id);
2398 }
2399
2400 let buffer_id = self.alloc_buffer_id();
2402
2403 let buffer = crate::model::buffer::Buffer::load_from_file(
2406 &canonical_path,
2407 self.config().editor.large_file_threshold_bytes as usize,
2408 std::sync::Arc::clone(&self.resources.local_filesystem),
2409 )?;
2410 let first_line = buffer.first_line_lossy();
2411 let detected =
2412 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
2413 &display_path,
2414 first_line.as_deref(),
2415 &self.resources.grammar_registry,
2416 &self.config().languages,
2417 self.config().default_language.as_deref(),
2418 );
2419 let state = crate::state::EditorState::from_buffer_with_language(buffer, detected);
2420
2421 self.buffers.insert(buffer_id, state);
2422 self.event_logs
2423 .insert(buffer_id, crate::model::event::EventLog::new());
2424
2425 let metadata = crate::app::types::BufferMetadata::with_file(
2427 path.to_path_buf(),
2428 &display_path,
2429 &self.root,
2430 self.authority().path_translation.as_ref(),
2431 );
2432 self.buffer_metadata.insert(buffer_id, metadata);
2433
2434 let target_split = self.preferred_split_for_file();
2436 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
2437 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
2438 let cfg = self.config().editor.clone();
2441 if let Some(view_state) = self
2442 .split_view_states_mut()
2443 .expect("active window must have a populated split layout")
2444 .get_mut(&target_split)
2445 {
2446 view_state.add_buffer(buffer_id);
2447 let buf_state = view_state.ensure_buffer_state(buffer_id);
2448 buf_state.apply_config_defaults(
2449 cfg.line_numbers,
2450 cfg.highlight_current_line,
2451 line_wrap,
2452 cfg.wrap_indent,
2453 wrap_column,
2454 cfg.rulers,
2455 );
2456 }
2457
2458 self.set_active_buffer(buffer_id);
2459
2460 let display_name = path.display().to_string();
2461 self.set_status_message(rust_i18n::t!("buffer.opened", name = display_name).to_string());
2462
2463 Ok(buffer_id)
2464 }
2465
2466 pub fn mark_buffer_read_only(&mut self, buffer_id: BufferId, read_only: bool) {
2470 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
2471 metadata.read_only = read_only;
2472 }
2473 if let Some(state) = self.buffers.get_mut(&buffer_id) {
2474 state.editing_disabled = read_only;
2475 }
2476 }
2477
2478 pub fn clear_warnings(&mut self) {
2481 self.warning_domains.general.clear();
2482 self.warning_domains.lsp.clear();
2483 self.set_status_message("Warnings cleared".to_string());
2484 }
2485
2486 pub fn update_lsp_warning_domain(&mut self) {
2490 let statuses = self.lsp_server_statuses.clone();
2492 self.warning_domains.lsp.update_from_statuses(&statuses);
2493 }
2494
2495 pub fn check_semantic_highlight_timer(&self) -> bool {
2500 self.buffers.any_needs_semantic_redraw()
2501 }
2502
2503 pub fn search_match_at_primary_cursor(&self) -> Option<std::ops::Range<usize>> {
2509 let search_state = self.search_state.as_ref()?;
2510 let pos = self.active_cursors().primary().position;
2511 let idx = match search_state.matches.binary_search(&pos) {
2512 Ok(i) => i,
2513 Err(0) => return None,
2514 Err(i) => i - 1,
2515 };
2516 let start = search_state.matches[idx];
2517 let len = *search_state.match_lengths.get(idx)?;
2518 if pos < start + len {
2519 Some(start..start + len)
2520 } else {
2521 None
2522 }
2523 }
2524
2525 pub fn update_search_highlights(
2529 &mut self,
2530 query: &str,
2531 search_fg: ratatui::style::Color,
2532 search_bg: ratatui::style::Color,
2533 ) {
2534 if query.is_empty() {
2535 self.clear_search_highlights();
2536 return;
2537 }
2538
2539 let case_sensitive = self.search_case_sensitive;
2540 let whole_word = self.search_whole_word;
2541 let use_regex = self.search_use_regex;
2542 let ns = self.search_namespace.clone();
2543
2544 let regex_pattern = if use_regex {
2545 if whole_word {
2546 format!(r"\b{}\b", query)
2547 } else {
2548 query.to_string()
2549 }
2550 } else {
2551 let escaped = regex::escape(query);
2552 if whole_word {
2553 format!(r"\b{}\b", escaped)
2554 } else {
2555 escaped
2556 }
2557 };
2558
2559 let regex = regex::RegexBuilder::new(®ex_pattern)
2560 .case_insensitive(!case_sensitive)
2561 .build();
2562 let regex = match regex {
2563 Ok(r) => r,
2564 Err(_) => {
2565 self.clear_search_highlights();
2566 return;
2567 }
2568 };
2569
2570 let active_split = self.effective_active_split();
2571 let (top_byte, visible_height) = self
2572 .buffers
2573 .splits()
2574 .expect("active window must have a populated split layout")
2575 .1
2576 .get(&active_split)
2577 .map(|vs| (vs.viewport.top_byte, vs.viewport.height.saturating_sub(2)))
2578 .unwrap_or((0, 20));
2579
2580 let state = self.active_state_mut();
2581 state.overlays.clear_namespace(&ns, &mut state.marker_list);
2582
2583 let visible_start = top_byte;
2584 let mut visible_end = top_byte;
2585 {
2586 let mut line_iter = state.buffer.line_iterator(top_byte, 80);
2587 for _ in 0..visible_height {
2588 if let Some((line_start, line_content)) = line_iter.next_line() {
2589 visible_end = line_start + line_content.len();
2590 } else {
2591 break;
2592 }
2593 }
2594 }
2595 visible_end = visible_end.min(state.buffer.len());
2596 let visible_text = state.get_text_range(visible_start, visible_end);
2597
2598 for mat in regex.find_iter(&visible_text) {
2599 let absolute_pos = visible_start + mat.start();
2600 let match_len = mat.end() - mat.start();
2601 let search_style = ratatui::style::Style::default().fg(search_fg).bg(search_bg);
2602 let overlay = crate::view::overlay::Overlay::with_namespace(
2603 &mut state.marker_list,
2604 absolute_pos..(absolute_pos + match_len),
2605 crate::view::overlay::OverlayFace::Style {
2606 style: search_style,
2607 },
2608 ns.clone(),
2609 )
2610 .with_priority_value(10);
2611 state.overlays.add(overlay);
2612 }
2613 }
2614
2615 pub fn file_explorer_is_visible(&self) -> bool {
2619 self.file_explorer_visible && self.file_explorer.is_some()
2620 }
2621
2622 pub fn file_explorer_extend_selection_up(&mut self) {
2624 if let Some(explorer) = self.file_explorer.as_mut() {
2625 explorer.extend_selection_up();
2626 }
2627 }
2628
2629 pub fn file_explorer_extend_selection_down(&mut self) {
2631 if let Some(explorer) = self.file_explorer.as_mut() {
2632 explorer.extend_selection_down();
2633 }
2634 }
2635
2636 pub fn file_explorer_toggle_select(&mut self) {
2638 if let Some(explorer) = self.file_explorer.as_mut() {
2639 explorer.toggle_select();
2640 }
2641 }
2642
2643 pub fn file_explorer_select_all(&mut self) {
2645 if let Some(explorer) = self.file_explorer.as_mut() {
2646 explorer.select_all();
2647 }
2648 }
2649
2650 pub fn file_explorer_search_push_char(&mut self, c: char) {
2652 if let Some(explorer) = self.file_explorer.as_mut() {
2653 explorer.search_push_char(c);
2654 explorer.update_scroll_for_selection();
2655 }
2656 }
2657
2658 pub fn file_explorer_search_pop_char(&mut self) {
2660 if let Some(explorer) = self.file_explorer.as_mut() {
2661 explorer.search_pop_char();
2662 explorer.update_scroll_for_selection();
2663 }
2664 }
2665
2666 pub fn schedule_folding_ranges_refresh(&mut self, buffer_id: BufferId) {
2672 const FOLDING_RANGES_DEBOUNCE_MS: u64 = 300;
2673 let next_time = std::time::Instant::now()
2674 + std::time::Duration::from_millis(FOLDING_RANGES_DEBOUNCE_MS);
2675 self.folding_ranges_debounce.insert(buffer_id, next_time);
2676 }
2677
2678 pub fn schedule_semantic_tokens_full_refresh(&mut self, buffer_id: BufferId) {
2682 const SEMANTIC_TOKENS_FULL_DEBOUNCE_MS: u64 = 500;
2683 if !self.resources.config.editor.enable_semantic_tokens_full {
2684 return;
2685 }
2686 let next_time = std::time::Instant::now()
2687 + std::time::Duration::from_millis(SEMANTIC_TOKENS_FULL_DEBOUNCE_MS);
2688 self.semantic_tokens_full_debounce
2689 .insert(buffer_id, next_time);
2690 }
2691
2692 pub(crate) fn send_lsp_changes_for_buffer(
2702 &mut self,
2703 buffer_id: BufferId,
2704 changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
2705 ) {
2706 const INLAY_HINTS_DEBOUNCE_MS: u64 = 500;
2707
2708 if changes.is_empty() {
2709 return;
2710 }
2711
2712 let metadata = match self.buffer_metadata.get(&buffer_id) {
2713 Some(m) => m,
2714 None => {
2715 tracing::debug!(
2716 "send_lsp_changes_for_buffer: no metadata for buffer {:?}",
2717 buffer_id
2718 );
2719 return;
2720 }
2721 };
2722
2723 if !metadata.lsp_enabled {
2724 tracing::debug!("send_lsp_changes_for_buffer: LSP disabled for this buffer");
2725 return;
2726 }
2727
2728 let uri = match metadata.file_uri() {
2729 Some(u) => u.clone(),
2730 None => {
2731 tracing::debug!(
2732 "send_lsp_changes_for_buffer: no URI for buffer (not a file or URI creation failed)"
2733 );
2734 return;
2735 }
2736 };
2737 let file_path = metadata.file_path().cloned();
2738
2739 let language = match self.buffers.get(&buffer_id).map(|s| s.language.clone()) {
2740 Some(l) => l,
2741 None => {
2742 tracing::debug!(
2743 "send_lsp_changes_for_buffer: no buffer state for {:?}",
2744 buffer_id
2745 );
2746 return;
2747 }
2748 };
2749
2750 tracing::trace!(
2751 "send_lsp_changes_for_buffer: sending {} changes to {} in single didChange notification",
2752 changes.len(),
2753 uri.as_str()
2754 );
2755
2756 use crate::services::lsp::manager::LspSpawnResult;
2757 let Some(lsp) = self.lsp.as_mut() else {
2758 tracing::debug!("send_lsp_changes_for_buffer: no LSP manager available");
2759 return;
2760 };
2761
2762 if lsp.try_spawn(&language, file_path.as_deref()) != LspSpawnResult::Spawned {
2763 tracing::debug!(
2764 "send_lsp_changes_for_buffer: LSP not running for {} (auto_start disabled)",
2765 language
2766 );
2767 return;
2768 }
2769
2770 let handles_needing_open: Vec<_> = {
2771 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
2772 return;
2773 };
2774 lsp.get_handles(&language)
2775 .into_iter()
2776 .filter(|sh| !metadata.lsp_opened_with.contains(&sh.handle.id()))
2777 .map(|sh| (sh.name.clone(), sh.handle.id()))
2778 .collect()
2779 };
2780
2781 if !handles_needing_open.is_empty() {
2782 let text = match self
2783 .buffers
2784 .get(&buffer_id)
2785 .and_then(|s| s.buffer.to_string())
2786 {
2787 Some(t) => t,
2788 None => {
2789 tracing::debug!(
2790 "send_lsp_changes_for_buffer: buffer text not available for didOpen"
2791 );
2792 return;
2793 }
2794 };
2795
2796 let Some(lsp) = self.lsp.as_mut() else {
2797 return;
2798 };
2799 for sh in lsp.get_handles_mut(&language) {
2800 if handles_needing_open
2801 .iter()
2802 .any(|(_, id)| *id == sh.handle.id())
2803 {
2804 if let Err(e) =
2805 sh.handle
2806 .did_open(uri.as_uri().clone(), text.clone(), language.clone())
2807 {
2808 tracing::warn!(
2809 "Failed to send didOpen to '{}' before didChange: {}",
2810 sh.name,
2811 e
2812 );
2813 } else {
2814 tracing::debug!(
2815 "Sent didOpen for {} to LSP handle '{}' before didChange",
2816 uri.as_str(),
2817 sh.name
2818 );
2819 }
2820 }
2821 }
2822
2823 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
2824 for (_, handle_id) in &handles_needing_open {
2825 metadata.lsp_opened_with.insert(*handle_id);
2826 }
2827 }
2828
2829 return;
2833 }
2834
2835 let Some(lsp) = self.lsp.as_mut() else {
2836 return;
2837 };
2838 let mut any_sent = false;
2839 for sh in lsp.get_handles_mut(&language) {
2840 if let Err(e) = sh.handle.did_change(uri.as_uri().clone(), changes.clone()) {
2841 tracing::warn!("Failed to send didChange to '{}': {}", sh.name, e);
2842 } else {
2843 any_sent = true;
2844 }
2845 }
2846 if any_sent {
2847 tracing::trace!("Successfully sent batched didChange to LSP");
2848
2849 if let Some(state) = self.buffers.get(&buffer_id) {
2850 if let Some(path) = state.buffer.file_path() {
2851 crate::services::lsp::diagnostics::invalidate_cache_for_file(
2852 &path.to_string_lossy(),
2853 );
2854 }
2855 }
2856
2857 self.scheduled_diagnostic_pull = Some((
2858 buffer_id,
2859 std::time::Instant::now() + std::time::Duration::from_millis(1000),
2860 ));
2861
2862 if self.resources.config.editor.enable_inlay_hints {
2863 self.scheduled_inlay_hints_request = Some((
2864 buffer_id,
2865 std::time::Instant::now()
2866 + std::time::Duration::from_millis(INLAY_HINTS_DEBOUNCE_MS),
2867 ));
2868 }
2869 }
2870 }
2871
2872 pub fn invalidate_layouts_for_buffer(&mut self, buffer_id: BufferId) {
2876 let Some((mgr, vs_map)) = self.buffers.splits_mut() else {
2877 return;
2878 };
2879 let splits_for_buffer = mgr.splits_for_buffer(buffer_id);
2880 for split_id in splits_for_buffer {
2881 if let Some(view_state) = vs_map.get_mut(&split_id) {
2882 view_state.invalidate_layout();
2883 view_state.view_transform = None;
2884 view_state.view_transform_stale = true;
2885 }
2886 }
2887 }
2888
2889 pub fn adjust_other_split_cursors_for_event(&mut self, event: &Event) {
2896 let current_buffer_id = self.active_buffer();
2897 let buffer_len = self
2898 .buffers
2899 .get(¤t_buffer_id)
2900 .map(|s| s.buffer.len())
2901 .unwrap_or(0);
2902 let Some((mgr, vs_map)) = self.buffers.splits_mut() else {
2903 return;
2904 };
2905 let current_split_id = mgr.active_split();
2906 let splits_for_buffer = mgr.splits_for_buffer(current_buffer_id);
2907
2908 if let Event::BulkEdit { new_cursors, .. } = event {
2909 for split_id in splits_for_buffer {
2910 if split_id == current_split_id {
2911 continue;
2912 }
2913 if let Some(view_state) = vs_map.get_mut(&split_id) {
2914 if let Some((_, pos, _)) = new_cursors.first() {
2915 let new_pos = (*pos).min(buffer_len);
2916 view_state.cursors.primary_mut().position = new_pos;
2917 view_state.cursors.primary_mut().anchor = None;
2918 }
2919 }
2920 }
2921 return;
2922 }
2923
2924 let adjustments: Vec<(usize, usize, usize)> = match event {
2925 Event::Insert { position, text, .. } => {
2926 vec![(*position, 0, text.len())]
2927 }
2928 Event::Delete { range, .. } => {
2929 vec![(range.start, range.len(), 0)]
2930 }
2931 Event::Batch { events, .. } => events
2932 .iter()
2933 .filter_map(|e| match e {
2934 Event::Insert { position, text, .. } => Some((*position, 0, text.len())),
2935 Event::Delete { range, .. } => Some((range.start, range.len(), 0)),
2936 _ => None,
2937 })
2938 .collect(),
2939 _ => Vec::new(),
2940 };
2941
2942 if adjustments.is_empty() {
2943 return;
2944 }
2945
2946 for split_id in splits_for_buffer {
2947 if split_id == current_split_id {
2948 continue;
2949 }
2950 if let Some(view_state) = vs_map.get_mut(&split_id) {
2951 for (edit_pos, old_len, new_len) in &adjustments {
2952 view_state
2953 .cursors
2954 .adjust_for_edit(*edit_pos, *old_len, *new_len);
2955 }
2956 }
2957 }
2958 }
2959
2960 pub(crate) fn handle_scroll_event(&mut self, line_offset: isize) {
2966 use crate::view::ui::view_pipeline::ViewLineIterator;
2967
2968 let Some((mgr, _)) = self.buffers.splits() else {
2969 return;
2970 };
2971 let active_split = mgr.active_split();
2972
2973 if let Some(group) = self
2974 .scroll_sync_manager
2975 .find_group_for_split(active_split.into())
2976 {
2977 let left = group.left_split;
2978 let right = group.right_split;
2979 if let Some(vs_map) = self.split_view_states_mut() {
2980 if let Some(vs) = vs_map.get_mut(&LeafId(left)) {
2981 vs.viewport.set_skip_ensure_visible();
2982 }
2983 if let Some(vs) = vs_map.get_mut(&LeafId(right)) {
2984 vs.viewport.set_skip_ensure_visible();
2985 }
2986 }
2987 }
2988
2989 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
2990 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
2991 let splits_to_scroll = if let Some(group_id) = sync_group {
2992 mgr.get_splits_in_group(group_id, vs_map)
2993 } else {
2994 vec![active_split]
2995 };
2996
2997 let tab_size = self.resources.config.editor.tab_size;
2998 for split_id in splits_to_scroll {
2999 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
3000 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
3001 continue;
3002 };
3003
3004 let view_transform_tokens = vs_map
3005 .get(&split_id)
3006 .and_then(|vs| vs.view_transform.as_ref())
3007 .map(|vt| vt.tokens.clone());
3008
3009 self.buffers
3010 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
3011 let soft_breaks = state.collect_soft_break_positions();
3012 let virtual_lines = state.collect_virtual_line_positions();
3013 let buffer = &mut state.buffer;
3014 if let Some(tokens) = view_transform_tokens {
3015 let view_lines: Vec<_> =
3016 ViewLineIterator::new(&tokens, false, false, tab_size, false).collect();
3017 view_state
3018 .viewport
3019 .scroll_view_lines(&view_lines, line_offset);
3020 } else if line_offset > 0 {
3021 view_state.viewport.scroll_down(
3022 buffer,
3023 &soft_breaks,
3024 &virtual_lines,
3025 line_offset as usize,
3026 );
3027 } else {
3028 view_state.viewport.scroll_up(
3029 buffer,
3030 &soft_breaks,
3031 &virtual_lines,
3032 line_offset.unsigned_abs(),
3033 );
3034 }
3035 view_state.viewport.set_skip_ensure_visible();
3036 });
3037 }
3038 }
3039
3040 pub(crate) fn handle_set_viewport_event(&mut self, top_line: usize) {
3042 let Some((mgr, _)) = self.buffers.splits() else {
3043 return;
3044 };
3045 let active_split = mgr.active_split();
3046
3047 if self
3048 .scroll_sync_manager
3049 .is_split_synced(active_split.into())
3050 {
3051 if let Some(group) = self
3052 .scroll_sync_manager
3053 .find_group_for_split_mut(active_split.into())
3054 {
3055 let scroll_line = if group.is_left_split(active_split.into()) {
3056 top_line
3057 } else {
3058 group.right_to_left_line(top_line)
3059 };
3060 group.set_scroll_line(scroll_line);
3061 }
3062
3063 let (left, right) = match self
3064 .scroll_sync_manager
3065 .find_group_for_split(active_split.into())
3066 {
3067 Some(group) => (group.left_split, group.right_split),
3068 None => return,
3069 };
3070 if let Some(vs_map) = self.split_view_states_mut() {
3071 if let Some(vs) = vs_map.get_mut(&LeafId(left)) {
3072 vs.viewport.set_skip_ensure_visible();
3073 }
3074 if let Some(vs) = vs_map.get_mut(&LeafId(right)) {
3075 vs.viewport.set_skip_ensure_visible();
3076 }
3077 }
3078 return;
3079 }
3080
3081 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
3082 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
3083 let splits_to_scroll = if let Some(group_id) = sync_group {
3084 mgr.get_splits_in_group(group_id, vs_map)
3085 } else {
3086 vec![active_split]
3087 };
3088
3089 for split_id in splits_to_scroll {
3090 let (mgr, _) = self.buffers.splits().expect("splits checked above");
3091 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
3092 continue;
3093 };
3094
3095 self.buffers
3096 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
3097 view_state.viewport.scroll_to(&mut state.buffer, top_line);
3098 view_state.viewport.set_skip_ensure_visible();
3099 });
3100 }
3101 }
3102
3103 pub(crate) fn handle_recenter_event(&mut self) {
3105 let Some((mgr, vs_map)) = self.buffers.splits() else {
3106 return;
3107 };
3108 let active_split = mgr.active_split();
3109
3110 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
3111 let splits_to_recenter = if let Some(group_id) = sync_group {
3112 mgr.get_splits_in_group(group_id, vs_map)
3113 } else {
3114 vec![active_split]
3115 };
3116
3117 for split_id in splits_to_recenter {
3118 let (mgr, _) = self.buffers.splits().expect("splits checked above");
3119 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
3120 continue;
3121 };
3122
3123 self.buffers
3124 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
3125 let buffer = &mut state.buffer;
3126 let cursor = *view_state.cursors.primary();
3127 let viewport_height = view_state.viewport.visible_line_count();
3128 let target_rows_from_top = viewport_height / 2;
3129
3130 let mut iter = buffer.line_iterator(cursor.position, 80);
3131 for _ in 0..target_rows_from_top {
3132 if iter.prev().is_none() {
3133 break;
3134 }
3135 }
3136 let new_top_byte = iter.current_position();
3137 view_state.viewport.top_byte = new_top_byte;
3138 view_state.viewport.set_skip_ensure_visible();
3139 });
3140 }
3141 }
3142
3143 pub fn set_pane_buffer(&mut self, leaf: LeafId, buffer_id: BufferId) {
3156 let (mgr, vs_map) = self
3157 .buffers
3158 .splits_mut()
3159 .expect("active window must have a populated split layout");
3160 mgr.set_split_buffer(leaf, buffer_id);
3161 if let Some(view_state) = vs_map.get_mut(&leaf) {
3162 view_state.switch_buffer(buffer_id);
3163 }
3164 }
3165}
3166
3167