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 }
1318 }
1319 state.buffer.set_modified(false);
1320 state.editing_disabled = true;
1321 state.margins.configure_for_line_numbers(false);
1322 });
1323 }
1324
1325 pub fn scroll_split_by_lines(
1333 &mut self,
1334 buffer_id: BufferId,
1335 leaf_id: LeafId,
1336 delta: i32,
1337 view_transform_tokens: Option<Vec<fresh_core::api::ViewTokenWire>>,
1338 tab_size: usize,
1339 ) {
1340 self.buffers
1341 .with_buffer_and_split(buffer_id, leaf_id, |state, view_state| {
1342 let soft_breaks = state.collect_soft_break_positions();
1343 let virtual_lines = state.collect_virtual_line_positions();
1344 let buffer = &mut state.buffer;
1345 let top_byte_before = view_state.viewport.top_byte;
1346 if let Some(tokens) = view_transform_tokens {
1347 use crate::view::ui::view_pipeline::ViewLineIterator;
1348 let view_lines: Vec<_> =
1349 ViewLineIterator::new(&tokens, false, false, tab_size, false).collect();
1350 view_state
1351 .viewport
1352 .scroll_view_lines(&view_lines, delta as isize);
1353 } else if delta < 0 {
1354 let lines_to_scroll = delta.unsigned_abs() as usize;
1355 view_state.viewport.scroll_up(
1356 buffer,
1357 &soft_breaks,
1358 &virtual_lines,
1359 lines_to_scroll,
1360 );
1361 } else {
1362 let lines_to_scroll = delta as usize;
1363 view_state.viewport.scroll_down(
1364 buffer,
1365 &soft_breaks,
1366 &virtual_lines,
1367 lines_to_scroll,
1368 );
1369 }
1370 view_state.viewport.set_skip_ensure_visible();
1371
1372 if let Some(folds) = view_state.keyed_states.get(&buffer_id).map(|bs| &bs.folds) {
1373 if !folds.is_empty() {
1374 let top_line = buffer.get_line_number(view_state.viewport.top_byte);
1375 if let Some(range) = folds
1376 .resolved_ranges(buffer, &state.marker_list)
1377 .iter()
1378 .find(|r| top_line >= r.start_line && top_line <= r.end_line)
1379 {
1380 let target_line = if delta >= 0 {
1381 range.end_line.saturating_add(1)
1382 } else {
1383 range.header_line
1384 };
1385 let target_byte = buffer
1386 .line_start_offset(target_line)
1387 .unwrap_or_else(|| buffer.len());
1388 view_state.viewport.top_byte = target_byte;
1389 view_state.viewport.top_view_line_offset = 0;
1390 }
1391 }
1392 }
1393 tracing::trace!(
1394 "scroll_split_by_lines: delta={}, top_byte {} -> {}",
1395 delta,
1396 top_byte_before,
1397 view_state.viewport.top_byte
1398 );
1399 });
1400 }
1401
1402 pub fn clear_lsp_overlays_for_buffer(
1406 &mut self,
1407 buffer_id: BufferId,
1408 diagnostic_namespace: &crate::model::event::OverlayNamespace,
1409 ) {
1410 self.buffers
1411 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1412 state
1413 .overlays
1414 .clear_namespace(diagnostic_namespace, &mut state.marker_list);
1415 state.virtual_texts.clear(&mut state.marker_list);
1416 state.folding_ranges.clear(&mut state.marker_list);
1417 for view_state in vs_map.values_mut() {
1418 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
1419 buf_state.folds.clear(&mut state.marker_list);
1420 }
1421 }
1422 });
1423 }
1424
1425 pub fn split_manager_mut(&mut self) -> Option<&mut SplitManager> {
1431 self.buffers.split_manager_mut()
1432 }
1433
1434 pub fn split_view_states_mut(&mut self) -> Option<&mut HashMap<LeafId, SplitViewState>> {
1436 self.buffers.split_view_states_mut()
1437 }
1438
1439 pub fn splits_mut(
1442 &mut self,
1443 ) -> Option<(&mut SplitManager, &mut HashMap<LeafId, SplitViewState>)> {
1444 self.buffers.splits_mut().map(|(m, vs)| (m, vs))
1445 }
1446
1447 pub fn new(
1455 id: WindowId,
1456 label: impl Into<String>,
1457 root: PathBuf,
1458 resources: WindowResources,
1459 ) -> Self {
1460 let mut label = label.into();
1461 if label.is_empty() {
1462 label = root
1463 .file_name()
1464 .and_then(|n| n.to_str())
1465 .map(str::to_owned)
1466 .unwrap_or_else(|| "main".to_owned());
1467 }
1468 let now = resources.time_source.now();
1475 Self {
1476 id,
1477 label,
1478 root,
1479 file_explorer: None,
1480 file_mod_times: HashMap::new(),
1481 plugin_state: HashMap::new(),
1482 lsp: None,
1483 panel_ids: HashMap::new(),
1484 buffers: WindowBuffers::new(),
1485 buffer_metadata: HashMap::new(),
1486 terminal_manager: crate::services::terminal::TerminalManager::new(),
1487 terminal_buffers: HashMap::new(),
1488 terminal_backing_files: HashMap::new(),
1489 terminal_log_files: HashMap::new(),
1490 event_logs: HashMap::new(),
1491 status_message: None,
1492 plugin_status_message: None,
1493 prompt: None,
1494 bridge: crate::services::async_bridge::AsyncBridge::new(),
1495 next_lsp_request_id: 0,
1496 pending_completion_requests: std::collections::HashSet::new(),
1497 completion_items: None,
1498 scheduled_completion_trigger: None,
1499 dabbrev_state: None,
1500 pending_goto_definition_request: None,
1501 pending_references_request: None,
1502 pending_references_symbol: String::new(),
1503 pending_signature_help_request: None,
1504 pending_code_actions_requests: std::collections::HashSet::new(),
1505 pending_code_actions_server_names: std::collections::HashMap::new(),
1506 pending_code_actions: None,
1507 pending_inlay_hints_requests: std::collections::HashMap::new(),
1508 pending_folding_range_requests: std::collections::HashMap::new(),
1509 folding_ranges_in_flight: std::collections::HashMap::new(),
1510 folding_ranges_debounce: std::collections::HashMap::new(),
1511 pending_semantic_token_requests: std::collections::HashMap::new(),
1512 semantic_tokens_in_flight: std::collections::HashMap::new(),
1513 semantic_tokens_full_debounce: std::collections::HashMap::new(),
1514 pending_semantic_token_range_requests: std::collections::HashMap::new(),
1515 semantic_tokens_range_in_flight: std::collections::HashMap::new(),
1516 semantic_tokens_range_last_request: std::collections::HashMap::new(),
1517 semantic_tokens_range_applied: std::collections::HashMap::new(),
1518 position_history: crate::input::position_history::PositionHistory::new(),
1519 in_navigation: false,
1520 suppress_position_history_once: false,
1521 bookmarks: crate::app::bookmarks::BookmarkState::default(),
1522 grouped_subtrees: HashMap::new(),
1523 composite_buffers: HashMap::new(),
1524 composite_view_states: HashMap::new(),
1525 layout_cache: WindowLayoutCache::default(),
1526 chrome_layout: ChromeLayout::default(),
1527 terminal_width: 80,
1528 terminal_height: 24,
1529 preview: None,
1530 terminal_mode: false,
1531 terminal_mode_resume: std::collections::HashSet::new(),
1532 seen_byte_ranges: HashMap::new(),
1533 previous_viewports: HashMap::new(),
1534 same_buffer_scroll_sync: false,
1535 interactive_replace_state: None,
1536 scroll_sync_manager: crate::view::scroll_sync::ScrollSyncManager::new(),
1537 file_explorer_visible: false,
1538 file_explorer_sync_in_progress: false,
1539 file_explorer_width: resources.config.file_explorer.width,
1540 file_explorer_side: resources.config.file_explorer.side,
1541 pending_file_explorer_show_hidden: None,
1542 pending_file_explorer_show_gitignored: None,
1543 file_explorer_decorations: HashMap::new(),
1544 file_explorer_decoration_cache:
1545 crate::view::file_tree::FileExplorerDecorationCache::default(),
1546 hover: crate::app::hover::HoverState::default(),
1547 search_state: None,
1548 search_namespace: crate::view::overlay::OverlayNamespace::from_string(
1549 "search".to_string(),
1550 ),
1551 pending_search_range: None,
1552 live_grep_last_state: None,
1553 overlay_preview_state: None,
1554 auto_revert_enabled: true,
1555 file_rapid_change_counts: HashMap::new(),
1556 goto_line_preview: None,
1557 pending_async_prompt_callback: None,
1558 pending_quit_unnamed_save: Vec::new(),
1559 search_case_sensitive: true,
1560 search_whole_word: false,
1561 search_use_regex: false,
1562 search_confirm_each: false,
1563 scheduled_diagnostic_pull: None,
1564 scheduled_inlay_hints_request: None,
1565 user_dismissed_lsp_languages: std::collections::HashSet::new(),
1566 editor_mode: None,
1567 prompt_histories: HashMap::new(),
1568 pending_close_buffer: None,
1569 completion_service: crate::services::completion::CompletionService::new(),
1570 lsp_diagnostic_namespace: crate::view::overlay::OverlayNamespace::from_string(
1571 "lsp-diagnostic".to_string(),
1572 ),
1573 diagnostic_result_ids: HashMap::new(),
1574 lsp_progress: HashMap::new(),
1575 lsp_server_statuses: HashMap::new(),
1576 lsp_menu_contributions: HashMap::new(),
1577 lsp_window_messages: Vec::new(),
1578 lsp_log_messages: Vec::new(),
1579 stored_push_diagnostics: HashMap::new(),
1580 stored_pull_diagnostics: HashMap::new(),
1581 stored_diagnostics: Arc::new(HashMap::new()),
1582 stored_folding_ranges: Arc::new(HashMap::new()),
1583 dir_mod_times: HashMap::new(),
1584 last_auto_revert_poll: now,
1585 last_file_tree_poll: now,
1586 git_index_resolved: false,
1587 pending_file_poll_rx: None,
1588 pending_dir_poll_rx: None,
1589 ephemeral_terminals: std::collections::HashSet::new(),
1590 plugin_dev_workspaces: HashMap::new(),
1591 status_bar_values: HashMap::new(),
1592 mouse_state: crate::app::types::MouseState::default(),
1593 key_context: crate::input::keybindings::KeyContext::Normal,
1594 chord_state: Vec::new(),
1595 previous_click_time: None,
1596 previous_click_position: None,
1597 click_count: 0,
1598 mouse_enabled: false,
1599 mouse_cursor_position: None,
1600 gpm_active: false,
1601 menu_bar_visible: resources.config.editor.show_menu_bar,
1602 menu_bar_auto_shown: false,
1603 tab_bar_visible: resources.config.editor.show_tab_bar,
1604 status_bar_visible: resources.config.editor.show_status_bar,
1605 prompt_line_visible: resources.config.editor.show_prompt_line,
1606 last_auto_recovery_save: now,
1607 last_persistent_auto_save: now,
1608 warning_domains: crate::app::warning_domains::WarningDomainRegistry::default(),
1609 tab_context_menu: None,
1610 file_explorer_context_menu: None,
1611 theme_info_popup: None,
1612 event_debug: None,
1613 file_open_state: None,
1614 file_browser_layout: None,
1615 buffer_groups: HashMap::new(),
1616 buffer_to_group: HashMap::new(),
1617 next_buffer_group_id: 0,
1618 pending_next_key_callbacks: std::collections::VecDeque::new(),
1619 key_capture_active: false,
1620 pending_key_capture_buffer: std::collections::VecDeque::new(),
1621 macros: crate::app::macros::MacroState::default(),
1622 active_custom_contexts: std::collections::HashSet::new(),
1623 keyboard_capture: false,
1624 review_hunks: Vec::new(),
1625 pending_file_opens: Vec::new(),
1626 pending_hot_exit_recovery: false,
1627 wait_tracking: HashMap::new(),
1628 completed_waits: Vec::new(),
1629 line_scan: crate::app::line_scan::LineScan::default(),
1630 search_scan: crate::app::search_scan::SearchScan::default(),
1631 search_overlay_top_byte: None,
1632 animations: crate::view::animation::AnimationRunner::default(),
1633 plugin_errors: Vec::new(),
1634 file_explorer_clipboard: None,
1635 process_groups: ProcessGroups::default(),
1636 resources,
1637 }
1638 }
1639
1640 pub fn config(&self) -> &crate::config::Config {
1650 &self.resources.config
1651 }
1652
1653 pub fn authority(&self) -> &crate::services::authority::Authority {
1655 &self.resources.authority
1656 }
1657
1658 pub fn alloc_buffer_id(&self) -> BufferId {
1660 self.resources.buffer_id_alloc.next()
1661 }
1662
1663 pub fn set_status_message(&mut self, message: String) {
1668 tracing::info!(target: "status", "{}", message);
1669 self.plugin_status_message = None;
1670 self.status_message = Some(message);
1671 }
1672
1673 pub fn clear_status_message(&mut self) {
1675 self.status_message = None;
1676 }
1677
1678 pub fn effective_active_pair(&self) -> (LeafId, BufferId) {
1688 let (mgr, vs_map) = self
1689 .buffers
1690 .splits()
1691 .expect("active window must have a populated split layout");
1692 let active_split = mgr.active_split();
1693 if let Some(vs) = vs_map.get(&active_split) {
1694 if vs.active_group_tab.is_some() {
1695 if let Some(inner_leaf) = vs.focused_group_leaf {
1696 if let Some(inner_vs) = vs_map.get(&inner_leaf) {
1697 let inner_buf = inner_vs.active_buffer;
1698 if self.buffers.get(&inner_buf).is_some()
1699 && inner_vs.keyed_states.contains_key(&inner_buf)
1700 {
1701 return (inner_leaf, inner_buf);
1702 }
1703 }
1704 }
1705 }
1706 }
1707 let outer_buf = mgr
1708 .active_buffer_id()
1709 .expect("Editor always has at least one buffer");
1710 if self.buffers.get(&outer_buf).is_some() {
1723 (active_split, outer_buf)
1724 } else if let Some(any) = self.buffers.find_id(|_, _| true) {
1725 tracing::warn!(
1726 stale_buffer_id = ?outer_buf,
1727 fallback_buffer_id = ?any,
1728 active_split = ?active_split,
1729 "effective_active_pair: split manager's active leaf points at \
1730 a BufferId missing from window.buffers (issue #1939). Falling \
1731 back to any live buffer; the split tree is in an inconsistent \
1732 state and should be repaired"
1733 );
1734 (active_split, any)
1735 } else {
1736 tracing::error!(
1740 stale_buffer_id = ?outer_buf,
1741 active_split = ?active_split,
1742 "effective_active_pair: window.buffers is empty AND the split \
1743 manager has a stale active buffer — no recovery possible, \
1744 next render will panic"
1745 );
1746 (active_split, outer_buf)
1747 }
1748 }
1749
1750 #[inline]
1752 pub fn active_buffer(&self) -> BufferId {
1753 let (_, buf) = self.effective_active_pair();
1754 buf
1755 }
1756
1757 pub fn effective_tabs_width(&self) -> u16 {
1761 if self.file_explorer_visible && self.file_explorer.is_some() {
1762 let explorer = self.file_explorer_width.to_cols(self.terminal_width);
1763 self.terminal_width.saturating_sub(explorer)
1764 } else {
1765 self.terminal_width
1766 }
1767 }
1768
1769 #[inline]
1772 pub fn effective_active_split(&self) -> LeafId {
1773 let (split, _) = self.effective_active_pair();
1774 split
1775 }
1776
1777 pub fn active_state(&self) -> &crate::state::EditorState {
1781 let buf = self.active_buffer();
1782 self.buffers
1783 .get(&buf)
1784 .expect("active buffer must be present in window")
1785 }
1786
1787 pub fn active_state_mut(&mut self) -> &mut crate::state::EditorState {
1789 let buf = self.active_buffer();
1790 self.buffers
1791 .get_mut(&buf)
1792 .expect("active buffer must be present in window")
1793 }
1794
1795 pub fn active_cursors(&self) -> &crate::model::cursor::Cursors {
1799 let split_id = self.effective_active_split();
1800 &self
1801 .buffers
1802 .splits()
1803 .expect("active window must have a populated split layout")
1804 .1
1805 .get(&split_id)
1806 .expect("active split must be in view-state map")
1807 .cursors
1808 }
1809
1810 pub fn active_cursors_mut(&mut self) -> &mut crate::model::cursor::Cursors {
1812 let split_id = self.effective_active_split();
1813 &mut self
1814 .buffers
1815 .splits_mut()
1816 .expect("active window must have a populated split layout")
1817 .1
1818 .get_mut(&split_id)
1819 .expect("active split must be in view-state map")
1820 .cursors
1821 }
1822
1823 pub fn active_event_log(&self) -> &crate::model::event::EventLog {
1825 let buf = self.active_buffer();
1826 self.event_logs
1827 .get(&buf)
1828 .expect("active buffer must have an event log")
1829 }
1830
1831 pub fn active_event_log_mut(&mut self) -> &mut crate::model::event::EventLog {
1833 let buf = self.active_buffer();
1834 self.event_logs
1835 .get_mut(&buf)
1836 .expect("active buffer must have an event log")
1837 }
1838
1839 pub fn promote_buffer_from_preview(&mut self, buffer_id: BufferId) {
1844 if let Some(m) = self.buffer_metadata.get_mut(&buffer_id) {
1845 m.is_preview = false;
1846 }
1847 if let Some((_, id)) = self.preview {
1848 if id == buffer_id {
1849 self.preview = None;
1850 }
1851 }
1852 }
1853
1854 pub fn promote_active_buffer_from_preview(&mut self) {
1857 let id = self.active_buffer();
1858 self.promote_buffer_from_preview(id);
1859 }
1860
1861 pub fn promote_current_preview(&mut self) {
1866 if let Some((_, id)) = self.preview.take() {
1867 if let Some(m) = self.buffer_metadata.get_mut(&id) {
1868 m.is_preview = false;
1869 }
1870 }
1871 }
1872
1873 pub fn promote_preview_if_not_in_split(&mut self, new_split: LeafId) {
1877 if let Some((preview_split, _)) = self.preview {
1878 if preview_split != new_split {
1879 self.promote_current_preview();
1880 }
1881 }
1882 }
1883
1884 pub fn is_buffer_preview(&self, buffer_id: BufferId) -> bool {
1889 self.buffer_metadata
1890 .get(&buffer_id)
1891 .map(|m| m.is_preview)
1892 .unwrap_or(false)
1893 }
1894
1895 pub fn current_preview(&self) -> Option<(LeafId, BufferId)> {
1898 self.preview
1899 }
1900
1901 pub fn is_terminal_buffer(&self, buffer_id: BufferId) -> bool {
1905 self.terminal_buffers.contains_key(&buffer_id)
1906 }
1907
1908 pub fn get_terminal_id(
1911 &self,
1912 buffer_id: BufferId,
1913 ) -> Option<crate::services::terminal::TerminalId> {
1914 self.terminal_buffers.get(&buffer_id).copied()
1915 }
1916
1917 pub fn clear_search_overlays(&mut self) {
1920 let ns = self.search_namespace.clone();
1921 let state = self.active_state_mut();
1922 state.overlays.clear_namespace(&ns, &mut state.marker_list);
1923 }
1924
1925 pub fn clear_search_highlights(&mut self) {
1928 self.clear_search_overlays();
1929 self.search_state = None;
1930 }
1931
1932 pub fn running_lsp_servers(&self) -> Vec<String> {
1935 self.lsp
1936 .as_ref()
1937 .map(|lsp| lsp.running_servers())
1938 .unwrap_or_default()
1939 }
1940
1941 pub fn pending_completion_requests_count(&self) -> usize {
1943 self.pending_completion_requests.len()
1944 }
1945
1946 pub fn completion_items_count(&self) -> usize {
1949 self.completion_items.as_ref().map_or(0, |v| v.len())
1950 }
1951
1952 pub fn initialized_lsp_server_count(&self, language: &str) -> usize {
1955 self.lsp
1956 .as_ref()
1957 .map(|lsp| {
1958 lsp.get_handles(language)
1959 .iter()
1960 .filter(|sh| sh.capabilities.initialized)
1961 .count()
1962 })
1963 .unwrap_or(0)
1964 }
1965
1966 pub fn shutdown_lsp_server(&mut self, language: &str) -> bool {
1970 self.lsp
1971 .as_mut()
1972 .map(|lsp| lsp.shutdown_server(language))
1973 .unwrap_or(false)
1974 }
1975
1976 pub fn enable_event_streaming<P: AsRef<std::path::Path>>(
1979 &mut self,
1980 path: P,
1981 ) -> anyhow::Result<()> {
1982 for event_log in self.event_logs.values_mut() {
1983 event_log.enable_streaming(&path)?;
1984 }
1985 Ok(())
1986 }
1987
1988 pub fn log_keystroke(&mut self, key_code: &str, modifiers: &str) {
1991 let buffer_id = self.active_buffer();
1992 if let Some(event_log) = self.event_logs.get_mut(&buffer_id) {
1993 event_log.log_keystroke(key_code, modifiers);
1994 }
1995 }
1996
1997 pub fn has_active_lsp_progress(&self) -> bool {
2000 !self.lsp_progress.is_empty()
2001 }
2002
2003 pub fn get_lsp_progress(&self) -> Vec<(String, String, Option<String>)> {
2006 self.lsp_progress
2007 .iter()
2008 .map(|(token, info)| (token.clone(), info.title.clone(), info.message.clone()))
2009 .collect()
2010 }
2011
2012 pub fn is_lsp_server_ready(&self, language: &str) -> bool {
2016 use crate::services::async_bridge::LspServerStatus;
2017 self.lsp_server_statuses
2018 .iter()
2019 .any(|((lang, server_name), status)| {
2020 if !matches!(status, LspServerStatus::Running) {
2021 return false;
2022 }
2023 if lang == language {
2024 return true;
2025 }
2026 self.lsp
2027 .as_ref()
2028 .and_then(|lsp| lsp.server_scope(server_name))
2029 .map(|scope| scope.accepts(language))
2030 .unwrap_or(false)
2031 })
2032 }
2033
2034 pub fn redirect_active_split_away_from_dock_if_needed(&mut self) {
2040 use crate::view::split::SplitRole;
2041 let Some((mgr, _)) = self.buffers.splits() else {
2042 return;
2043 };
2044 let active = mgr.active_split();
2045 if mgr.leaf_role(active) != Some(SplitRole::UtilityDock) {
2046 return;
2047 }
2048 let is_editor_leaf = |leaf| mgr.leaf_role(leaf) != Some(SplitRole::UtilityDock);
2049 let target = mgr.last_focused_where(is_editor_leaf).or_else(|| {
2050 mgr.root()
2051 .leaf_split_ids()
2052 .into_iter()
2053 .find(|leaf| is_editor_leaf(*leaf))
2054 });
2055 let Some(target) = target else {
2056 return;
2057 };
2058 if target == active {
2059 return;
2060 }
2061 self.split_manager_mut()
2062 .expect("active window must have a populated split layout")
2063 .set_active_split(target);
2064 }
2065
2066 pub fn restore_global_file_state(
2071 &mut self,
2072 buffer_id: BufferId,
2073 path: &std::path::Path,
2074 split_id: LeafId,
2075 ) {
2076 use crate::workspace::PersistedFileWorkspace;
2077
2078 let file_state = match PersistedFileWorkspace::load(path) {
2079 Some(state) => state,
2080 None => return,
2081 };
2082
2083 self.restore_buffer_state_in_split(buffer_id, split_id, &file_state);
2084 }
2085
2086 pub fn save_file_state_on_close(&self, buffer_id: BufferId) {
2091 use crate::workspace::{
2092 PersistedFileWorkspace, SerializedCursor, SerializedFileState, SerializedScroll,
2093 };
2094
2095 let abs_path = match self.buffer_metadata.get(&buffer_id) {
2096 Some(metadata) => match metadata.file_path() {
2097 Some(path) => path.to_path_buf(),
2098 None => return,
2099 },
2100 None => return,
2101 };
2102
2103 let view_state = self
2104 .buffers
2105 .splits()
2106 .expect("active window must have a populated split layout")
2107 .1
2108 .values()
2109 .find(|vs| vs.has_buffer(buffer_id));
2110
2111 let view_state = match view_state {
2112 Some(vs) => vs,
2113 None => return,
2114 };
2115
2116 let buf_state = match view_state.keyed_states.get(&buffer_id) {
2117 Some(bs) => bs,
2118 None => return,
2119 };
2120
2121 let primary_cursor = buf_state.cursors.primary();
2122 let file_state = SerializedFileState {
2123 cursor: SerializedCursor {
2124 position: primary_cursor.position,
2125 anchor: primary_cursor.anchor,
2126 sticky_column: primary_cursor.sticky_column,
2127 },
2128 additional_cursors: buf_state
2129 .cursors
2130 .iter()
2131 .skip(1)
2132 .map(|(_, cursor)| SerializedCursor {
2133 position: cursor.position,
2134 anchor: cursor.anchor,
2135 sticky_column: cursor.sticky_column,
2136 })
2137 .collect(),
2138 scroll: SerializedScroll {
2139 top_byte: buf_state.viewport.top_byte,
2140 top_view_line_offset: buf_state.viewport.top_view_line_offset,
2141 left_column: buf_state.viewport.left_column,
2142 },
2143 view_mode: Default::default(),
2144 compose_width: None,
2145 plugin_state: std::collections::HashMap::new(),
2146 folds: Vec::new(),
2147 };
2148
2149 PersistedFileWorkspace::save(&abs_path, file_state);
2150 tracing::debug!("Saved file state on close for {:?}", abs_path);
2151 }
2152
2153 pub(crate) fn take_pending_semantic_token_request(
2155 &mut self,
2156 request_id: u64,
2157 ) -> Option<crate::app::SemanticTokenFullRequest> {
2158 if let Some(request) = self.pending_semantic_token_requests.remove(&request_id) {
2159 self.semantic_tokens_in_flight.remove(&request.buffer_id);
2160 Some(request)
2161 } else {
2162 None
2163 }
2164 }
2165
2166 pub(crate) fn take_pending_semantic_token_range_request(
2168 &mut self,
2169 request_id: u64,
2170 ) -> Option<crate::app::SemanticTokenRangeRequest> {
2171 if let Some(request) = self
2172 .pending_semantic_token_range_requests
2173 .remove(&request_id)
2174 {
2175 self.semantic_tokens_range_in_flight
2176 .remove(&request.buffer_id);
2177 Some(request)
2178 } else {
2179 None
2180 }
2181 }
2182
2183 pub fn move_cursor_to_visible_area(&mut self, split_id: LeafId, buffer_id: BufferId) {
2186 let (top_byte, viewport_height) =
2187 if let Some(view_state) = self.buffers.splits().and_then(|(_, vs)| vs.get(&split_id)) {
2188 (
2189 view_state.viewport.top_byte,
2190 view_state.viewport.height as usize,
2191 )
2192 } else {
2193 return;
2194 };
2195
2196 if let Some(state) = self.buffers.get_mut(&buffer_id) {
2197 let buffer_len = state.buffer.len();
2198
2199 let mut iter = state.buffer.line_iterator(top_byte, 80);
2200 let mut bottom_byte = buffer_len;
2201
2202 for _ in 0..viewport_height {
2203 if let Some((pos, line)) = iter.next_line() {
2204 bottom_byte = pos + line.len();
2205 } else {
2206 bottom_byte = buffer_len;
2207 break;
2208 }
2209 }
2210
2211 if let Some(view_state) = self
2212 .split_view_states_mut()
2213 .and_then(|vs| vs.get_mut(&split_id))
2214 {
2215 let cursor_pos = view_state.cursors.primary().position;
2216 if cursor_pos < top_byte || cursor_pos > bottom_byte {
2217 let cursor = view_state.cursors.primary_mut();
2218 cursor.position = top_byte;
2219 }
2222 }
2223 }
2224 }
2225
2226 pub fn calculate_max_scroll_position(
2231 buffer: &mut crate::model::buffer::Buffer,
2232 viewport_height: usize,
2233 ) -> usize {
2234 if viewport_height == 0 {
2235 return 0;
2236 }
2237
2238 let buffer_len = buffer.len();
2239 if buffer_len == 0 {
2240 return 0;
2241 }
2242
2243 let mut line_count = 0;
2244 let mut iter = buffer.line_iterator(0, 80);
2245 while iter.next_line().is_some() {
2246 line_count += 1;
2247 }
2248
2249 if line_count <= viewport_height {
2250 return 0;
2251 }
2252
2253 let scrollable_lines = line_count.saturating_sub(viewport_height);
2254
2255 let mut iter = buffer.line_iterator(0, 80);
2256 let mut current_line = 0;
2257 let mut max_byte_pos = 0;
2258
2259 while current_line < scrollable_lines {
2260 if let Some((pos, _content)) = iter.next_line() {
2261 max_byte_pos = pos;
2262 current_line += 1;
2263 } else {
2264 break;
2265 }
2266 }
2267
2268 max_byte_pos
2269 }
2270
2271 pub fn split_at_position(&self, col: u16, row: u16) -> Option<(LeafId, BufferId)> {
2276 for &(split_id, buffer_id, content_rect, scrollbar_rect, _, _) in
2277 &self.layout_cache.split_areas
2278 {
2279 let in_content = col >= content_rect.x
2280 && col < content_rect.x + content_rect.width
2281 && row >= content_rect.y
2282 && row < content_rect.y + content_rect.height;
2283 let in_scrollbar = scrollbar_rect.width > 0
2284 && scrollbar_rect.height > 0
2285 && col >= scrollbar_rect.x
2286 && col < scrollbar_rect.x + scrollbar_rect.width
2287 && row >= scrollbar_rect.y
2288 && row < scrollbar_rect.y + scrollbar_rect.height;
2289 if in_content || in_scrollbar {
2290 return Some((split_id, buffer_id));
2291 }
2292 }
2293 None
2294 }
2295
2296 pub fn check_diagnostic_pull_timer(&mut self) -> bool {
2301 let Some((buffer_id, trigger_time)) = self.scheduled_diagnostic_pull else {
2302 return false;
2303 };
2304
2305 if std::time::Instant::now() < trigger_time {
2306 return false;
2307 }
2308
2309 self.scheduled_diagnostic_pull = None;
2310
2311 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
2312 return false;
2313 };
2314 let Some(uri) = metadata.file_uri().cloned() else {
2315 return false;
2316 };
2317 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
2318 return false;
2319 };
2320
2321 let previous_result_id = self.diagnostic_result_ids.get(uri.as_str()).cloned();
2322 let request_id = self.next_lsp_request_id;
2323 self.next_lsp_request_id += 1;
2324
2325 let Some(lsp) = self.lsp.as_mut() else {
2326 return false;
2327 };
2328 let Some(sh) = lsp.handle_for_feature_mut(&language, crate::types::LspFeature::Diagnostics)
2329 else {
2330 return false;
2331 };
2332 if let Err(e) =
2333 sh.handle
2334 .document_diagnostic(request_id, uri.as_uri().clone(), previous_result_id)
2335 {
2336 tracing::debug!(
2337 "Failed to pull diagnostics after edit for {}: {}",
2338 uri.as_str(),
2339 e
2340 );
2341 } else {
2342 tracing::debug!(
2343 "Pulling diagnostics after edit for {} (request_id={})",
2344 uri.as_str(),
2345 request_id
2346 );
2347 }
2348
2349 false
2350 }
2351
2352 pub fn open_local_file(&mut self, path: &std::path::Path) -> anyhow::Result<BufferId> {
2360 let resolved_path = if path.is_relative() {
2362 self.root.join(path)
2363 } else {
2364 path.to_path_buf()
2365 };
2366
2367 let display_path = resolved_path.clone();
2369
2370 let canonical_path = resolved_path
2372 .canonicalize()
2373 .unwrap_or_else(|_| resolved_path.clone());
2374 let path = canonical_path.as_path();
2375
2376 let already_open = self
2378 .buffers
2379 .iter()
2380 .find(|(_, state)| state.buffer.file_path() == Some(path))
2381 .map(|(id, _)| *id);
2382
2383 if let Some(id) = already_open {
2384 self.set_active_buffer(id);
2385 return Ok(id);
2386 }
2387
2388 let buffer_id = self.alloc_buffer_id();
2390
2391 let buffer = crate::model::buffer::Buffer::load_from_file(
2394 &canonical_path,
2395 self.config().editor.large_file_threshold_bytes as usize,
2396 std::sync::Arc::clone(&self.resources.local_filesystem),
2397 )?;
2398 let first_line = buffer.first_line_lossy();
2399 let detected =
2400 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
2401 &display_path,
2402 first_line.as_deref(),
2403 &self.resources.grammar_registry,
2404 &self.config().languages,
2405 self.config().default_language.as_deref(),
2406 );
2407 let state = crate::state::EditorState::from_buffer_with_language(buffer, detected);
2408
2409 self.buffers.insert(buffer_id, state);
2410 self.event_logs
2411 .insert(buffer_id, crate::model::event::EventLog::new());
2412
2413 let metadata = crate::app::types::BufferMetadata::with_file(
2415 path.to_path_buf(),
2416 &display_path,
2417 &self.root,
2418 self.authority().path_translation.as_ref(),
2419 );
2420 self.buffer_metadata.insert(buffer_id, metadata);
2421
2422 let target_split = self.preferred_split_for_file();
2424 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
2425 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
2426 let cfg = self.config().editor.clone();
2429 if let Some(view_state) = self
2430 .split_view_states_mut()
2431 .expect("active window must have a populated split layout")
2432 .get_mut(&target_split)
2433 {
2434 view_state.add_buffer(buffer_id);
2435 let buf_state = view_state.ensure_buffer_state(buffer_id);
2436 buf_state.apply_config_defaults(
2437 cfg.line_numbers,
2438 cfg.highlight_current_line,
2439 line_wrap,
2440 cfg.wrap_indent,
2441 wrap_column,
2442 cfg.rulers,
2443 );
2444 }
2445
2446 self.set_active_buffer(buffer_id);
2447
2448 let display_name = path.display().to_string();
2449 self.set_status_message(rust_i18n::t!("buffer.opened", name = display_name).to_string());
2450
2451 Ok(buffer_id)
2452 }
2453
2454 pub fn mark_buffer_read_only(&mut self, buffer_id: BufferId, read_only: bool) {
2458 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
2459 metadata.read_only = read_only;
2460 }
2461 if let Some(state) = self.buffers.get_mut(&buffer_id) {
2462 state.editing_disabled = read_only;
2463 }
2464 }
2465
2466 pub fn clear_warnings(&mut self) {
2469 self.warning_domains.general.clear();
2470 self.warning_domains.lsp.clear();
2471 self.set_status_message("Warnings cleared".to_string());
2472 }
2473
2474 pub fn update_lsp_warning_domain(&mut self) {
2478 let statuses = self.lsp_server_statuses.clone();
2480 self.warning_domains.lsp.update_from_statuses(&statuses);
2481 }
2482
2483 pub fn check_semantic_highlight_timer(&self) -> bool {
2488 self.buffers.any_needs_semantic_redraw()
2489 }
2490
2491 pub fn search_match_at_primary_cursor(&self) -> Option<std::ops::Range<usize>> {
2497 let search_state = self.search_state.as_ref()?;
2498 let pos = self.active_cursors().primary().position;
2499 let idx = match search_state.matches.binary_search(&pos) {
2500 Ok(i) => i,
2501 Err(0) => return None,
2502 Err(i) => i - 1,
2503 };
2504 let start = search_state.matches[idx];
2505 let len = *search_state.match_lengths.get(idx)?;
2506 if pos < start + len {
2507 Some(start..start + len)
2508 } else {
2509 None
2510 }
2511 }
2512
2513 pub fn update_search_highlights(
2517 &mut self,
2518 query: &str,
2519 search_fg: ratatui::style::Color,
2520 search_bg: ratatui::style::Color,
2521 ) {
2522 if query.is_empty() {
2523 self.clear_search_highlights();
2524 return;
2525 }
2526
2527 let case_sensitive = self.search_case_sensitive;
2528 let whole_word = self.search_whole_word;
2529 let use_regex = self.search_use_regex;
2530 let ns = self.search_namespace.clone();
2531
2532 let regex_pattern = if use_regex {
2533 if whole_word {
2534 format!(r"\b{}\b", query)
2535 } else {
2536 query.to_string()
2537 }
2538 } else {
2539 let escaped = regex::escape(query);
2540 if whole_word {
2541 format!(r"\b{}\b", escaped)
2542 } else {
2543 escaped
2544 }
2545 };
2546
2547 let regex = regex::RegexBuilder::new(®ex_pattern)
2548 .case_insensitive(!case_sensitive)
2549 .build();
2550 let regex = match regex {
2551 Ok(r) => r,
2552 Err(_) => {
2553 self.clear_search_highlights();
2554 return;
2555 }
2556 };
2557
2558 let active_split = self.effective_active_split();
2559 let (top_byte, visible_height) = self
2560 .buffers
2561 .splits()
2562 .expect("active window must have a populated split layout")
2563 .1
2564 .get(&active_split)
2565 .map(|vs| (vs.viewport.top_byte, vs.viewport.height.saturating_sub(2)))
2566 .unwrap_or((0, 20));
2567
2568 let state = self.active_state_mut();
2569 state.overlays.clear_namespace(&ns, &mut state.marker_list);
2570
2571 let visible_start = top_byte;
2572 let mut visible_end = top_byte;
2573 {
2574 let mut line_iter = state.buffer.line_iterator(top_byte, 80);
2575 for _ in 0..visible_height {
2576 if let Some((line_start, line_content)) = line_iter.next_line() {
2577 visible_end = line_start + line_content.len();
2578 } else {
2579 break;
2580 }
2581 }
2582 }
2583 visible_end = visible_end.min(state.buffer.len());
2584 let visible_text = state.get_text_range(visible_start, visible_end);
2585
2586 for mat in regex.find_iter(&visible_text) {
2587 let absolute_pos = visible_start + mat.start();
2588 let match_len = mat.end() - mat.start();
2589 let search_style = ratatui::style::Style::default().fg(search_fg).bg(search_bg);
2590 let overlay = crate::view::overlay::Overlay::with_namespace(
2591 &mut state.marker_list,
2592 absolute_pos..(absolute_pos + match_len),
2593 crate::view::overlay::OverlayFace::Style {
2594 style: search_style,
2595 },
2596 ns.clone(),
2597 )
2598 .with_priority_value(10);
2599 state.overlays.add(overlay);
2600 }
2601 }
2602
2603 pub fn file_explorer_is_visible(&self) -> bool {
2607 self.file_explorer_visible && self.file_explorer.is_some()
2608 }
2609
2610 pub fn file_explorer_extend_selection_up(&mut self) {
2612 if let Some(explorer) = self.file_explorer.as_mut() {
2613 explorer.extend_selection_up();
2614 }
2615 }
2616
2617 pub fn file_explorer_extend_selection_down(&mut self) {
2619 if let Some(explorer) = self.file_explorer.as_mut() {
2620 explorer.extend_selection_down();
2621 }
2622 }
2623
2624 pub fn file_explorer_toggle_select(&mut self) {
2626 if let Some(explorer) = self.file_explorer.as_mut() {
2627 explorer.toggle_select();
2628 }
2629 }
2630
2631 pub fn file_explorer_select_all(&mut self) {
2633 if let Some(explorer) = self.file_explorer.as_mut() {
2634 explorer.select_all();
2635 }
2636 }
2637
2638 pub fn file_explorer_search_push_char(&mut self, c: char) {
2640 if let Some(explorer) = self.file_explorer.as_mut() {
2641 explorer.search_push_char(c);
2642 explorer.update_scroll_for_selection();
2643 }
2644 }
2645
2646 pub fn file_explorer_search_pop_char(&mut self) {
2648 if let Some(explorer) = self.file_explorer.as_mut() {
2649 explorer.search_pop_char();
2650 explorer.update_scroll_for_selection();
2651 }
2652 }
2653
2654 pub fn schedule_folding_ranges_refresh(&mut self, buffer_id: BufferId) {
2660 const FOLDING_RANGES_DEBOUNCE_MS: u64 = 300;
2661 let next_time = std::time::Instant::now()
2662 + std::time::Duration::from_millis(FOLDING_RANGES_DEBOUNCE_MS);
2663 self.folding_ranges_debounce.insert(buffer_id, next_time);
2664 }
2665
2666 pub fn schedule_semantic_tokens_full_refresh(&mut self, buffer_id: BufferId) {
2670 const SEMANTIC_TOKENS_FULL_DEBOUNCE_MS: u64 = 500;
2671 if !self.resources.config.editor.enable_semantic_tokens_full {
2672 return;
2673 }
2674 let next_time = std::time::Instant::now()
2675 + std::time::Duration::from_millis(SEMANTIC_TOKENS_FULL_DEBOUNCE_MS);
2676 self.semantic_tokens_full_debounce
2677 .insert(buffer_id, next_time);
2678 }
2679
2680 pub(crate) fn send_lsp_changes_for_buffer(
2690 &mut self,
2691 buffer_id: BufferId,
2692 changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
2693 ) {
2694 const INLAY_HINTS_DEBOUNCE_MS: u64 = 500;
2695
2696 if changes.is_empty() {
2697 return;
2698 }
2699
2700 let metadata = match self.buffer_metadata.get(&buffer_id) {
2701 Some(m) => m,
2702 None => {
2703 tracing::debug!(
2704 "send_lsp_changes_for_buffer: no metadata for buffer {:?}",
2705 buffer_id
2706 );
2707 return;
2708 }
2709 };
2710
2711 if !metadata.lsp_enabled {
2712 tracing::debug!("send_lsp_changes_for_buffer: LSP disabled for this buffer");
2713 return;
2714 }
2715
2716 let uri = match metadata.file_uri() {
2717 Some(u) => u.clone(),
2718 None => {
2719 tracing::debug!(
2720 "send_lsp_changes_for_buffer: no URI for buffer (not a file or URI creation failed)"
2721 );
2722 return;
2723 }
2724 };
2725 let file_path = metadata.file_path().cloned();
2726
2727 let language = match self.buffers.get(&buffer_id).map(|s| s.language.clone()) {
2728 Some(l) => l,
2729 None => {
2730 tracing::debug!(
2731 "send_lsp_changes_for_buffer: no buffer state for {:?}",
2732 buffer_id
2733 );
2734 return;
2735 }
2736 };
2737
2738 tracing::trace!(
2739 "send_lsp_changes_for_buffer: sending {} changes to {} in single didChange notification",
2740 changes.len(),
2741 uri.as_str()
2742 );
2743
2744 use crate::services::lsp::manager::LspSpawnResult;
2745 let Some(lsp) = self.lsp.as_mut() else {
2746 tracing::debug!("send_lsp_changes_for_buffer: no LSP manager available");
2747 return;
2748 };
2749
2750 if lsp.try_spawn(&language, file_path.as_deref()) != LspSpawnResult::Spawned {
2751 tracing::debug!(
2752 "send_lsp_changes_for_buffer: LSP not running for {} (auto_start disabled)",
2753 language
2754 );
2755 return;
2756 }
2757
2758 let handles_needing_open: Vec<_> = {
2759 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
2760 return;
2761 };
2762 lsp.get_handles(&language)
2763 .into_iter()
2764 .filter(|sh| !metadata.lsp_opened_with.contains(&sh.handle.id()))
2765 .map(|sh| (sh.name.clone(), sh.handle.id()))
2766 .collect()
2767 };
2768
2769 if !handles_needing_open.is_empty() {
2770 let text = match self
2771 .buffers
2772 .get(&buffer_id)
2773 .and_then(|s| s.buffer.to_string())
2774 {
2775 Some(t) => t,
2776 None => {
2777 tracing::debug!(
2778 "send_lsp_changes_for_buffer: buffer text not available for didOpen"
2779 );
2780 return;
2781 }
2782 };
2783
2784 let Some(lsp) = self.lsp.as_mut() else {
2785 return;
2786 };
2787 for sh in lsp.get_handles_mut(&language) {
2788 if handles_needing_open
2789 .iter()
2790 .any(|(_, id)| *id == sh.handle.id())
2791 {
2792 if let Err(e) =
2793 sh.handle
2794 .did_open(uri.as_uri().clone(), text.clone(), language.clone())
2795 {
2796 tracing::warn!(
2797 "Failed to send didOpen to '{}' before didChange: {}",
2798 sh.name,
2799 e
2800 );
2801 } else {
2802 tracing::debug!(
2803 "Sent didOpen for {} to LSP handle '{}' before didChange",
2804 uri.as_str(),
2805 sh.name
2806 );
2807 }
2808 }
2809 }
2810
2811 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
2812 for (_, handle_id) in &handles_needing_open {
2813 metadata.lsp_opened_with.insert(*handle_id);
2814 }
2815 }
2816
2817 return;
2821 }
2822
2823 let Some(lsp) = self.lsp.as_mut() else {
2824 return;
2825 };
2826 let mut any_sent = false;
2827 for sh in lsp.get_handles_mut(&language) {
2828 if let Err(e) = sh.handle.did_change(uri.as_uri().clone(), changes.clone()) {
2829 tracing::warn!("Failed to send didChange to '{}': {}", sh.name, e);
2830 } else {
2831 any_sent = true;
2832 }
2833 }
2834 if any_sent {
2835 tracing::trace!("Successfully sent batched didChange to LSP");
2836
2837 if let Some(state) = self.buffers.get(&buffer_id) {
2838 if let Some(path) = state.buffer.file_path() {
2839 crate::services::lsp::diagnostics::invalidate_cache_for_file(
2840 &path.to_string_lossy(),
2841 );
2842 }
2843 }
2844
2845 self.scheduled_diagnostic_pull = Some((
2846 buffer_id,
2847 std::time::Instant::now() + std::time::Duration::from_millis(1000),
2848 ));
2849
2850 if self.resources.config.editor.enable_inlay_hints {
2851 self.scheduled_inlay_hints_request = Some((
2852 buffer_id,
2853 std::time::Instant::now()
2854 + std::time::Duration::from_millis(INLAY_HINTS_DEBOUNCE_MS),
2855 ));
2856 }
2857 }
2858 }
2859
2860 pub fn invalidate_layouts_for_buffer(&mut self, buffer_id: BufferId) {
2864 let Some((mgr, vs_map)) = self.buffers.splits_mut() else {
2865 return;
2866 };
2867 let splits_for_buffer = mgr.splits_for_buffer(buffer_id);
2868 for split_id in splits_for_buffer {
2869 if let Some(view_state) = vs_map.get_mut(&split_id) {
2870 view_state.invalidate_layout();
2871 view_state.view_transform = None;
2872 view_state.view_transform_stale = true;
2873 }
2874 }
2875 }
2876
2877 pub fn adjust_other_split_cursors_for_event(&mut self, event: &Event) {
2884 let current_buffer_id = self.active_buffer();
2885 let buffer_len = self
2886 .buffers
2887 .get(¤t_buffer_id)
2888 .map(|s| s.buffer.len())
2889 .unwrap_or(0);
2890 let Some((mgr, vs_map)) = self.buffers.splits_mut() else {
2891 return;
2892 };
2893 let current_split_id = mgr.active_split();
2894 let splits_for_buffer = mgr.splits_for_buffer(current_buffer_id);
2895
2896 if let Event::BulkEdit { new_cursors, .. } = event {
2897 for split_id in splits_for_buffer {
2898 if split_id == current_split_id {
2899 continue;
2900 }
2901 if let Some(view_state) = vs_map.get_mut(&split_id) {
2902 if let Some((_, pos, _)) = new_cursors.first() {
2903 let new_pos = (*pos).min(buffer_len);
2904 view_state.cursors.primary_mut().position = new_pos;
2905 view_state.cursors.primary_mut().anchor = None;
2906 }
2907 }
2908 }
2909 return;
2910 }
2911
2912 let adjustments: Vec<(usize, usize, usize)> = match event {
2913 Event::Insert { position, text, .. } => {
2914 vec![(*position, 0, text.len())]
2915 }
2916 Event::Delete { range, .. } => {
2917 vec![(range.start, range.len(), 0)]
2918 }
2919 Event::Batch { events, .. } => events
2920 .iter()
2921 .filter_map(|e| match e {
2922 Event::Insert { position, text, .. } => Some((*position, 0, text.len())),
2923 Event::Delete { range, .. } => Some((range.start, range.len(), 0)),
2924 _ => None,
2925 })
2926 .collect(),
2927 _ => Vec::new(),
2928 };
2929
2930 if adjustments.is_empty() {
2931 return;
2932 }
2933
2934 for split_id in splits_for_buffer {
2935 if split_id == current_split_id {
2936 continue;
2937 }
2938 if let Some(view_state) = vs_map.get_mut(&split_id) {
2939 for (edit_pos, old_len, new_len) in &adjustments {
2940 view_state
2941 .cursors
2942 .adjust_for_edit(*edit_pos, *old_len, *new_len);
2943 }
2944 }
2945 }
2946 }
2947
2948 pub(crate) fn handle_scroll_event(&mut self, line_offset: isize) {
2954 use crate::view::ui::view_pipeline::ViewLineIterator;
2955
2956 let Some((mgr, _)) = self.buffers.splits() else {
2957 return;
2958 };
2959 let active_split = mgr.active_split();
2960
2961 if let Some(group) = self
2962 .scroll_sync_manager
2963 .find_group_for_split(active_split.into())
2964 {
2965 let left = group.left_split;
2966 let right = group.right_split;
2967 if let Some(vs_map) = self.split_view_states_mut() {
2968 if let Some(vs) = vs_map.get_mut(&LeafId(left)) {
2969 vs.viewport.set_skip_ensure_visible();
2970 }
2971 if let Some(vs) = vs_map.get_mut(&LeafId(right)) {
2972 vs.viewport.set_skip_ensure_visible();
2973 }
2974 }
2975 }
2976
2977 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
2978 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
2979 let splits_to_scroll = if let Some(group_id) = sync_group {
2980 mgr.get_splits_in_group(group_id, vs_map)
2981 } else {
2982 vec![active_split]
2983 };
2984
2985 let tab_size = self.resources.config.editor.tab_size;
2986 for split_id in splits_to_scroll {
2987 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
2988 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
2989 continue;
2990 };
2991
2992 let view_transform_tokens = vs_map
2993 .get(&split_id)
2994 .and_then(|vs| vs.view_transform.as_ref())
2995 .map(|vt| vt.tokens.clone());
2996
2997 self.buffers
2998 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
2999 let soft_breaks = state.collect_soft_break_positions();
3000 let virtual_lines = state.collect_virtual_line_positions();
3001 let buffer = &mut state.buffer;
3002 if let Some(tokens) = view_transform_tokens {
3003 let view_lines: Vec<_> =
3004 ViewLineIterator::new(&tokens, false, false, tab_size, false).collect();
3005 view_state
3006 .viewport
3007 .scroll_view_lines(&view_lines, line_offset);
3008 } else if line_offset > 0 {
3009 view_state.viewport.scroll_down(
3010 buffer,
3011 &soft_breaks,
3012 &virtual_lines,
3013 line_offset as usize,
3014 );
3015 } else {
3016 view_state.viewport.scroll_up(
3017 buffer,
3018 &soft_breaks,
3019 &virtual_lines,
3020 line_offset.unsigned_abs(),
3021 );
3022 }
3023 view_state.viewport.set_skip_ensure_visible();
3024 });
3025 }
3026 }
3027
3028 pub(crate) fn handle_set_viewport_event(&mut self, top_line: usize) {
3030 let Some((mgr, _)) = self.buffers.splits() else {
3031 return;
3032 };
3033 let active_split = mgr.active_split();
3034
3035 if self
3036 .scroll_sync_manager
3037 .is_split_synced(active_split.into())
3038 {
3039 if let Some(group) = self
3040 .scroll_sync_manager
3041 .find_group_for_split_mut(active_split.into())
3042 {
3043 let scroll_line = if group.is_left_split(active_split.into()) {
3044 top_line
3045 } else {
3046 group.right_to_left_line(top_line)
3047 };
3048 group.set_scroll_line(scroll_line);
3049 }
3050
3051 let (left, right) = match self
3052 .scroll_sync_manager
3053 .find_group_for_split(active_split.into())
3054 {
3055 Some(group) => (group.left_split, group.right_split),
3056 None => return,
3057 };
3058 if let Some(vs_map) = self.split_view_states_mut() {
3059 if let Some(vs) = vs_map.get_mut(&LeafId(left)) {
3060 vs.viewport.set_skip_ensure_visible();
3061 }
3062 if let Some(vs) = vs_map.get_mut(&LeafId(right)) {
3063 vs.viewport.set_skip_ensure_visible();
3064 }
3065 }
3066 return;
3067 }
3068
3069 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
3070 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
3071 let splits_to_scroll = if let Some(group_id) = sync_group {
3072 mgr.get_splits_in_group(group_id, vs_map)
3073 } else {
3074 vec![active_split]
3075 };
3076
3077 for split_id in splits_to_scroll {
3078 let (mgr, _) = self.buffers.splits().expect("splits checked above");
3079 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
3080 continue;
3081 };
3082
3083 self.buffers
3084 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
3085 view_state.viewport.scroll_to(&mut state.buffer, top_line);
3086 view_state.viewport.set_skip_ensure_visible();
3087 });
3088 }
3089 }
3090
3091 pub(crate) fn handle_recenter_event(&mut self) {
3093 let Some((mgr, vs_map)) = self.buffers.splits() else {
3094 return;
3095 };
3096 let active_split = mgr.active_split();
3097
3098 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
3099 let splits_to_recenter = if let Some(group_id) = sync_group {
3100 mgr.get_splits_in_group(group_id, vs_map)
3101 } else {
3102 vec![active_split]
3103 };
3104
3105 for split_id in splits_to_recenter {
3106 let (mgr, _) = self.buffers.splits().expect("splits checked above");
3107 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
3108 continue;
3109 };
3110
3111 self.buffers
3112 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
3113 let buffer = &mut state.buffer;
3114 let cursor = *view_state.cursors.primary();
3115 let viewport_height = view_state.viewport.visible_line_count();
3116 let target_rows_from_top = viewport_height / 2;
3117
3118 let mut iter = buffer.line_iterator(cursor.position, 80);
3119 for _ in 0..target_rows_from_top {
3120 if iter.prev().is_none() {
3121 break;
3122 }
3123 }
3124 let new_top_byte = iter.current_position();
3125 view_state.viewport.top_byte = new_top_byte;
3126 view_state.viewport.set_skip_ensure_visible();
3127 });
3128 }
3129 }
3130
3131 pub fn set_pane_buffer(&mut self, leaf: LeafId, buffer_id: BufferId) {
3144 let (mgr, vs_map) = self
3145 .buffers
3146 .splits_mut()
3147 .expect("active window must have a populated split layout");
3148 mgr.set_split_buffer(leaf, buffer_id);
3149 if let Some(view_state) = vs_map.get_mut(&leaf) {
3150 view_state.switch_buffer(buffer_id);
3151 }
3152 }
3153}
3154
3155