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 scroll_overlay_preview_by_lines(&mut self, delta: i32) -> bool {
1420 let buffer_id = match self.overlay_preview_state.as_ref() {
1421 Some(ps) if !ps.blanked => ps.buffer_id,
1422 _ => return false,
1423 };
1424 let (soft_breaks, virtual_lines) = match self.buffers.get(&buffer_id) {
1427 Some(s) => (
1428 s.collect_soft_break_positions(),
1429 s.collect_virtual_line_positions(),
1430 ),
1431 None => return false,
1432 };
1433 let Some(state) = self.buffers.get_mut(&buffer_id) else {
1434 return false;
1435 };
1436 let buffer = &mut state.buffer;
1437 let Some(ps) = self.overlay_preview_state.as_mut() else {
1438 return false;
1439 };
1440 let viewport = &mut ps.view_state.active_state_mut().viewport;
1441 if delta < 0 {
1442 viewport.scroll_up(
1443 buffer,
1444 &soft_breaks,
1445 &virtual_lines,
1446 delta.unsigned_abs() as usize,
1447 );
1448 } else {
1449 viewport.scroll_down(buffer, &soft_breaks, &virtual_lines, delta as usize);
1450 }
1451 viewport.set_skip_ensure_visible();
1452 true
1453 }
1454
1455 pub fn clear_lsp_overlays_for_buffer(
1459 &mut self,
1460 buffer_id: BufferId,
1461 diagnostic_namespace: &crate::model::event::OverlayNamespace,
1462 ) {
1463 self.buffers
1464 .with_buffer_and_view_states(buffer_id, |state, vs_map| {
1465 state
1466 .overlays
1467 .clear_namespace(diagnostic_namespace, &mut state.marker_list);
1468 state.virtual_texts.clear(&mut state.marker_list);
1469 state.folding_ranges.clear(&mut state.marker_list);
1470 for view_state in vs_map.values_mut() {
1471 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
1472 buf_state.folds.clear(&mut state.marker_list);
1473 }
1474 }
1475 });
1476 }
1477
1478 pub fn split_manager_mut(&mut self) -> Option<&mut SplitManager> {
1484 self.buffers.split_manager_mut()
1485 }
1486
1487 pub fn split_view_states_mut(&mut self) -> Option<&mut HashMap<LeafId, SplitViewState>> {
1489 self.buffers.split_view_states_mut()
1490 }
1491
1492 pub fn splits_mut(
1495 &mut self,
1496 ) -> Option<(&mut SplitManager, &mut HashMap<LeafId, SplitViewState>)> {
1497 self.buffers.splits_mut().map(|(m, vs)| (m, vs))
1498 }
1499
1500 pub fn new(
1508 id: WindowId,
1509 label: impl Into<String>,
1510 root: PathBuf,
1511 resources: WindowResources,
1512 ) -> Self {
1513 let mut label = label.into();
1514 if label.is_empty() {
1515 label = root
1516 .file_name()
1517 .and_then(|n| n.to_str())
1518 .map(str::to_owned)
1519 .unwrap_or_else(|| "main".to_owned());
1520 }
1521 let now = resources.time_source.now();
1528 Self {
1529 id,
1530 label,
1531 root,
1532 file_explorer: None,
1533 file_mod_times: HashMap::new(),
1534 plugin_state: HashMap::new(),
1535 lsp: None,
1536 panel_ids: HashMap::new(),
1537 buffers: WindowBuffers::new(),
1538 buffer_metadata: HashMap::new(),
1539 terminal_manager: crate::services::terminal::TerminalManager::new(),
1540 terminal_buffers: HashMap::new(),
1541 terminal_backing_files: HashMap::new(),
1542 terminal_log_files: HashMap::new(),
1543 event_logs: HashMap::new(),
1544 status_message: None,
1545 plugin_status_message: None,
1546 prompt: None,
1547 bridge: crate::services::async_bridge::AsyncBridge::new(),
1548 next_lsp_request_id: 0,
1549 pending_completion_requests: std::collections::HashSet::new(),
1550 completion_items: None,
1551 scheduled_completion_trigger: None,
1552 dabbrev_state: None,
1553 pending_goto_definition_request: None,
1554 pending_references_request: None,
1555 pending_references_symbol: String::new(),
1556 pending_signature_help_request: None,
1557 pending_code_actions_requests: std::collections::HashSet::new(),
1558 pending_code_actions_server_names: std::collections::HashMap::new(),
1559 pending_code_actions: None,
1560 pending_inlay_hints_requests: std::collections::HashMap::new(),
1561 pending_folding_range_requests: std::collections::HashMap::new(),
1562 folding_ranges_in_flight: std::collections::HashMap::new(),
1563 folding_ranges_debounce: std::collections::HashMap::new(),
1564 pending_semantic_token_requests: std::collections::HashMap::new(),
1565 semantic_tokens_in_flight: std::collections::HashMap::new(),
1566 semantic_tokens_full_debounce: std::collections::HashMap::new(),
1567 pending_semantic_token_range_requests: std::collections::HashMap::new(),
1568 semantic_tokens_range_in_flight: std::collections::HashMap::new(),
1569 semantic_tokens_range_last_request: std::collections::HashMap::new(),
1570 semantic_tokens_range_applied: std::collections::HashMap::new(),
1571 position_history: crate::input::position_history::PositionHistory::new(),
1572 in_navigation: false,
1573 suppress_position_history_once: false,
1574 bookmarks: crate::app::bookmarks::BookmarkState::default(),
1575 grouped_subtrees: HashMap::new(),
1576 composite_buffers: HashMap::new(),
1577 composite_view_states: HashMap::new(),
1578 layout_cache: WindowLayoutCache::default(),
1579 chrome_layout: ChromeLayout::default(),
1580 terminal_width: 80,
1581 terminal_height: 24,
1582 preview: None,
1583 terminal_mode: false,
1584 terminal_mode_resume: std::collections::HashSet::new(),
1585 seen_byte_ranges: HashMap::new(),
1586 previous_viewports: HashMap::new(),
1587 same_buffer_scroll_sync: false,
1588 interactive_replace_state: None,
1589 scroll_sync_manager: crate::view::scroll_sync::ScrollSyncManager::new(),
1590 file_explorer_visible: false,
1591 file_explorer_sync_in_progress: false,
1592 file_explorer_width: resources.config.file_explorer.width,
1593 file_explorer_side: resources.config.file_explorer.side,
1594 pending_file_explorer_show_hidden: None,
1595 pending_file_explorer_show_gitignored: None,
1596 file_explorer_decorations: HashMap::new(),
1597 file_explorer_decoration_cache:
1598 crate::view::file_tree::FileExplorerDecorationCache::default(),
1599 hover: crate::app::hover::HoverState::default(),
1600 search_state: None,
1601 search_namespace: crate::view::overlay::OverlayNamespace::from_string(
1602 "search".to_string(),
1603 ),
1604 pending_search_range: None,
1605 live_grep_last_state: None,
1606 overlay_preview_state: None,
1607 auto_revert_enabled: true,
1608 file_rapid_change_counts: HashMap::new(),
1609 goto_line_preview: None,
1610 pending_async_prompt_callback: None,
1611 pending_quit_unnamed_save: Vec::new(),
1612 search_case_sensitive: true,
1613 search_whole_word: false,
1614 search_use_regex: false,
1615 search_confirm_each: false,
1616 scheduled_diagnostic_pull: None,
1617 scheduled_inlay_hints_request: None,
1618 user_dismissed_lsp_languages: std::collections::HashSet::new(),
1619 editor_mode: None,
1620 prompt_histories: HashMap::new(),
1621 pending_close_buffer: None,
1622 completion_service: crate::services::completion::CompletionService::new(),
1623 lsp_diagnostic_namespace: crate::view::overlay::OverlayNamespace::from_string(
1624 "lsp-diagnostic".to_string(),
1625 ),
1626 diagnostic_result_ids: HashMap::new(),
1627 lsp_progress: HashMap::new(),
1628 lsp_server_statuses: HashMap::new(),
1629 lsp_menu_contributions: HashMap::new(),
1630 lsp_window_messages: Vec::new(),
1631 lsp_log_messages: Vec::new(),
1632 stored_push_diagnostics: HashMap::new(),
1633 stored_pull_diagnostics: HashMap::new(),
1634 stored_diagnostics: Arc::new(HashMap::new()),
1635 stored_folding_ranges: Arc::new(HashMap::new()),
1636 dir_mod_times: HashMap::new(),
1637 last_auto_revert_poll: now,
1638 last_file_tree_poll: now,
1639 git_index_resolved: false,
1640 pending_file_poll_rx: None,
1641 pending_dir_poll_rx: None,
1642 ephemeral_terminals: std::collections::HashSet::new(),
1643 plugin_dev_workspaces: HashMap::new(),
1644 status_bar_values: HashMap::new(),
1645 mouse_state: crate::app::types::MouseState::default(),
1646 key_context: crate::input::keybindings::KeyContext::Normal,
1647 chord_state: Vec::new(),
1648 previous_click_time: None,
1649 previous_click_position: None,
1650 click_count: 0,
1651 mouse_enabled: false,
1652 mouse_cursor_position: None,
1653 gpm_active: false,
1654 menu_bar_visible: resources.config.editor.show_menu_bar,
1655 menu_bar_auto_shown: false,
1656 tab_bar_visible: resources.config.editor.show_tab_bar,
1657 status_bar_visible: resources.config.editor.show_status_bar,
1658 prompt_line_visible: resources.config.editor.show_prompt_line,
1659 last_auto_recovery_save: now,
1660 last_persistent_auto_save: now,
1661 warning_domains: crate::app::warning_domains::WarningDomainRegistry::default(),
1662 tab_context_menu: None,
1663 file_explorer_context_menu: None,
1664 theme_info_popup: None,
1665 event_debug: None,
1666 file_open_state: None,
1667 file_browser_layout: None,
1668 buffer_groups: HashMap::new(),
1669 buffer_to_group: HashMap::new(),
1670 next_buffer_group_id: 0,
1671 pending_next_key_callbacks: std::collections::VecDeque::new(),
1672 key_capture_active: false,
1673 pending_key_capture_buffer: std::collections::VecDeque::new(),
1674 macros: crate::app::macros::MacroState::default(),
1675 active_custom_contexts: std::collections::HashSet::new(),
1676 keyboard_capture: false,
1677 review_hunks: Vec::new(),
1678 pending_file_opens: Vec::new(),
1679 pending_hot_exit_recovery: false,
1680 wait_tracking: HashMap::new(),
1681 completed_waits: Vec::new(),
1682 line_scan: crate::app::line_scan::LineScan::default(),
1683 search_scan: crate::app::search_scan::SearchScan::default(),
1684 search_overlay_top_byte: None,
1685 animations: crate::view::animation::AnimationRunner::default(),
1686 plugin_errors: Vec::new(),
1687 file_explorer_clipboard: None,
1688 process_groups: ProcessGroups::default(),
1689 resources,
1690 }
1691 }
1692
1693 pub fn config(&self) -> &crate::config::Config {
1703 &self.resources.config
1704 }
1705
1706 pub fn authority(&self) -> &crate::services::authority::Authority {
1708 &self.resources.authority
1709 }
1710
1711 pub fn alloc_buffer_id(&self) -> BufferId {
1713 self.resources.buffer_id_alloc.next()
1714 }
1715
1716 pub fn set_status_message(&mut self, message: String) {
1721 tracing::info!(target: "status", "{}", message);
1722 self.plugin_status_message = None;
1723 self.status_message = Some(message);
1724 }
1725
1726 pub fn clear_status_message(&mut self) {
1728 self.status_message = None;
1729 }
1730
1731 pub fn effective_active_pair(&self) -> (LeafId, BufferId) {
1741 let (mgr, vs_map) = self
1742 .buffers
1743 .splits()
1744 .expect("active window must have a populated split layout");
1745 let active_split = mgr.active_split();
1746 if let Some(vs) = vs_map.get(&active_split) {
1747 if vs.active_group_tab.is_some() {
1748 if let Some(inner_leaf) = vs.focused_group_leaf {
1749 if let Some(inner_vs) = vs_map.get(&inner_leaf) {
1750 let inner_buf = inner_vs.active_buffer;
1751 if self.buffers.get(&inner_buf).is_some()
1752 && inner_vs.keyed_states.contains_key(&inner_buf)
1753 {
1754 return (inner_leaf, inner_buf);
1755 }
1756 }
1757 }
1758 }
1759 }
1760 let outer_buf = mgr
1761 .active_buffer_id()
1762 .expect("Editor always has at least one buffer");
1763 if self.buffers.get(&outer_buf).is_some() {
1776 (active_split, outer_buf)
1777 } else if let Some(any) = self.buffers.find_id(|_, _| true) {
1778 tracing::warn!(
1779 stale_buffer_id = ?outer_buf,
1780 fallback_buffer_id = ?any,
1781 active_split = ?active_split,
1782 "effective_active_pair: split manager's active leaf points at \
1783 a BufferId missing from window.buffers (issue #1939). Falling \
1784 back to any live buffer; the split tree is in an inconsistent \
1785 state and should be repaired"
1786 );
1787 (active_split, any)
1788 } else {
1789 tracing::error!(
1793 stale_buffer_id = ?outer_buf,
1794 active_split = ?active_split,
1795 "effective_active_pair: window.buffers is empty AND the split \
1796 manager has a stale active buffer — no recovery possible, \
1797 next render will panic"
1798 );
1799 (active_split, outer_buf)
1800 }
1801 }
1802
1803 #[inline]
1805 pub fn active_buffer(&self) -> BufferId {
1806 let (_, buf) = self.effective_active_pair();
1807 buf
1808 }
1809
1810 pub fn effective_tabs_width(&self) -> u16 {
1814 if self.file_explorer_visible && self.file_explorer.is_some() {
1815 let explorer = self.file_explorer_width.to_cols(self.terminal_width);
1816 self.terminal_width.saturating_sub(explorer)
1817 } else {
1818 self.terminal_width
1819 }
1820 }
1821
1822 #[inline]
1825 pub fn effective_active_split(&self) -> LeafId {
1826 let (split, _) = self.effective_active_pair();
1827 split
1828 }
1829
1830 pub fn active_state(&self) -> &crate::state::EditorState {
1834 let buf = self.active_buffer();
1835 self.buffers
1836 .get(&buf)
1837 .expect("active buffer must be present in window")
1838 }
1839
1840 pub fn active_state_mut(&mut self) -> &mut crate::state::EditorState {
1842 let buf = self.active_buffer();
1843 self.buffers
1844 .get_mut(&buf)
1845 .expect("active buffer must be present in window")
1846 }
1847
1848 pub fn active_cursors(&self) -> &crate::model::cursor::Cursors {
1852 let split_id = self.effective_active_split();
1853 &self
1854 .buffers
1855 .splits()
1856 .expect("active window must have a populated split layout")
1857 .1
1858 .get(&split_id)
1859 .expect("active split must be in view-state map")
1860 .cursors
1861 }
1862
1863 pub fn active_cursors_mut(&mut self) -> &mut crate::model::cursor::Cursors {
1865 let split_id = self.effective_active_split();
1866 &mut self
1867 .buffers
1868 .splits_mut()
1869 .expect("active window must have a populated split layout")
1870 .1
1871 .get_mut(&split_id)
1872 .expect("active split must be in view-state map")
1873 .cursors
1874 }
1875
1876 pub fn active_event_log(&self) -> &crate::model::event::EventLog {
1878 let buf = self.active_buffer();
1879 self.event_logs
1880 .get(&buf)
1881 .expect("active buffer must have an event log")
1882 }
1883
1884 pub fn active_event_log_mut(&mut self) -> &mut crate::model::event::EventLog {
1886 let buf = self.active_buffer();
1887 self.event_logs
1888 .get_mut(&buf)
1889 .expect("active buffer must have an event log")
1890 }
1891
1892 pub fn promote_buffer_from_preview(&mut self, buffer_id: BufferId) {
1897 if let Some(m) = self.buffer_metadata.get_mut(&buffer_id) {
1898 m.is_preview = false;
1899 }
1900 if let Some((_, id)) = self.preview {
1901 if id == buffer_id {
1902 self.preview = None;
1903 }
1904 }
1905 }
1906
1907 pub fn promote_active_buffer_from_preview(&mut self) {
1910 let id = self.active_buffer();
1911 self.promote_buffer_from_preview(id);
1912 }
1913
1914 pub fn promote_current_preview(&mut self) {
1919 if let Some((_, id)) = self.preview.take() {
1920 if let Some(m) = self.buffer_metadata.get_mut(&id) {
1921 m.is_preview = false;
1922 }
1923 }
1924 }
1925
1926 pub fn promote_preview_if_not_in_split(&mut self, new_split: LeafId) {
1930 if let Some((preview_split, _)) = self.preview {
1931 if preview_split != new_split {
1932 self.promote_current_preview();
1933 }
1934 }
1935 }
1936
1937 pub fn is_buffer_preview(&self, buffer_id: BufferId) -> bool {
1942 self.buffer_metadata
1943 .get(&buffer_id)
1944 .map(|m| m.is_preview)
1945 .unwrap_or(false)
1946 }
1947
1948 pub fn current_preview(&self) -> Option<(LeafId, BufferId)> {
1951 self.preview
1952 }
1953
1954 pub fn is_terminal_buffer(&self, buffer_id: BufferId) -> bool {
1958 self.terminal_buffers.contains_key(&buffer_id)
1959 }
1960
1961 pub fn get_terminal_id(
1964 &self,
1965 buffer_id: BufferId,
1966 ) -> Option<crate::services::terminal::TerminalId> {
1967 self.terminal_buffers.get(&buffer_id).copied()
1968 }
1969
1970 pub fn clear_search_overlays(&mut self) {
1973 let ns = self.search_namespace.clone();
1974 let state = self.active_state_mut();
1975 state.overlays.clear_namespace(&ns, &mut state.marker_list);
1976 }
1977
1978 pub fn clear_search_highlights(&mut self) {
1981 self.clear_search_overlays();
1982 self.search_state = None;
1983 }
1984
1985 pub fn running_lsp_servers(&self) -> Vec<String> {
1988 self.lsp
1989 .as_ref()
1990 .map(|lsp| lsp.running_servers())
1991 .unwrap_or_default()
1992 }
1993
1994 pub fn pending_completion_requests_count(&self) -> usize {
1996 self.pending_completion_requests.len()
1997 }
1998
1999 pub fn completion_items_count(&self) -> usize {
2002 self.completion_items.as_ref().map_or(0, |v| v.len())
2003 }
2004
2005 pub fn initialized_lsp_server_count(&self, language: &str) -> usize {
2008 self.lsp
2009 .as_ref()
2010 .map(|lsp| {
2011 lsp.get_handles(language)
2012 .iter()
2013 .filter(|sh| sh.capabilities.initialized)
2014 .count()
2015 })
2016 .unwrap_or(0)
2017 }
2018
2019 pub fn shutdown_lsp_server(&mut self, language: &str) -> bool {
2023 self.lsp
2024 .as_mut()
2025 .map(|lsp| lsp.shutdown_server(language))
2026 .unwrap_or(false)
2027 }
2028
2029 pub fn enable_event_streaming<P: AsRef<std::path::Path>>(
2032 &mut self,
2033 path: P,
2034 ) -> anyhow::Result<()> {
2035 for event_log in self.event_logs.values_mut() {
2036 event_log.enable_streaming(&path)?;
2037 }
2038 Ok(())
2039 }
2040
2041 pub fn log_keystroke(&mut self, key_code: &str, modifiers: &str) {
2044 let buffer_id = self.active_buffer();
2045 if let Some(event_log) = self.event_logs.get_mut(&buffer_id) {
2046 event_log.log_keystroke(key_code, modifiers);
2047 }
2048 }
2049
2050 pub fn has_active_lsp_progress(&self) -> bool {
2053 !self.lsp_progress.is_empty()
2054 }
2055
2056 pub fn get_lsp_progress(&self) -> Vec<(String, String, Option<String>)> {
2059 self.lsp_progress
2060 .iter()
2061 .map(|(token, info)| (token.clone(), info.title.clone(), info.message.clone()))
2062 .collect()
2063 }
2064
2065 pub fn is_lsp_server_ready(&self, language: &str) -> bool {
2069 use crate::services::async_bridge::LspServerStatus;
2070 self.lsp_server_statuses
2071 .iter()
2072 .any(|((lang, server_name), status)| {
2073 if !matches!(status, LspServerStatus::Running) {
2074 return false;
2075 }
2076 if lang == language {
2077 return true;
2078 }
2079 self.lsp
2080 .as_ref()
2081 .and_then(|lsp| lsp.server_scope(server_name))
2082 .map(|scope| scope.accepts(language))
2083 .unwrap_or(false)
2084 })
2085 }
2086
2087 pub fn redirect_active_split_away_from_dock_if_needed(&mut self) {
2093 use crate::view::split::SplitRole;
2094 let Some((mgr, _)) = self.buffers.splits() else {
2095 return;
2096 };
2097 let active = mgr.active_split();
2098 if mgr.leaf_role(active) != Some(SplitRole::UtilityDock) {
2099 return;
2100 }
2101 let is_editor_leaf = |leaf| mgr.leaf_role(leaf) != Some(SplitRole::UtilityDock);
2102 let target = mgr.last_focused_where(is_editor_leaf).or_else(|| {
2103 mgr.root()
2104 .leaf_split_ids()
2105 .into_iter()
2106 .find(|leaf| is_editor_leaf(*leaf))
2107 });
2108 let Some(target) = target else {
2109 return;
2110 };
2111 if target == active {
2112 return;
2113 }
2114 self.split_manager_mut()
2115 .expect("active window must have a populated split layout")
2116 .set_active_split(target);
2117 }
2118
2119 pub fn restore_global_file_state(
2124 &mut self,
2125 buffer_id: BufferId,
2126 path: &std::path::Path,
2127 split_id: LeafId,
2128 ) {
2129 use crate::workspace::PersistedFileWorkspace;
2130
2131 let file_state = match PersistedFileWorkspace::load(path) {
2132 Some(state) => state,
2133 None => return,
2134 };
2135
2136 self.restore_buffer_state_in_split(buffer_id, split_id, &file_state);
2137 }
2138
2139 pub fn save_file_state_on_close(&self, buffer_id: BufferId) {
2144 use crate::workspace::{
2145 PersistedFileWorkspace, SerializedCursor, SerializedFileState, SerializedScroll,
2146 };
2147
2148 let abs_path = match self.buffer_metadata.get(&buffer_id) {
2149 Some(metadata) => match metadata.file_path() {
2150 Some(path) => path.to_path_buf(),
2151 None => return,
2152 },
2153 None => return,
2154 };
2155
2156 let view_state = self
2157 .buffers
2158 .splits()
2159 .expect("active window must have a populated split layout")
2160 .1
2161 .values()
2162 .find(|vs| vs.has_buffer(buffer_id));
2163
2164 let view_state = match view_state {
2165 Some(vs) => vs,
2166 None => return,
2167 };
2168
2169 let buf_state = match view_state.keyed_states.get(&buffer_id) {
2170 Some(bs) => bs,
2171 None => return,
2172 };
2173
2174 let primary_cursor = buf_state.cursors.primary();
2175 let file_state = SerializedFileState {
2176 cursor: SerializedCursor {
2177 position: primary_cursor.position,
2178 anchor: primary_cursor.anchor,
2179 sticky_column: primary_cursor.sticky_column,
2180 },
2181 additional_cursors: buf_state
2182 .cursors
2183 .iter()
2184 .skip(1)
2185 .map(|(_, cursor)| SerializedCursor {
2186 position: cursor.position,
2187 anchor: cursor.anchor,
2188 sticky_column: cursor.sticky_column,
2189 })
2190 .collect(),
2191 scroll: SerializedScroll {
2192 top_byte: buf_state.viewport.top_byte,
2193 top_view_line_offset: buf_state.viewport.top_view_line_offset,
2194 left_column: buf_state.viewport.left_column,
2195 },
2196 view_mode: Default::default(),
2197 compose_width: None,
2198 plugin_state: std::collections::HashMap::new(),
2199 folds: Vec::new(),
2200 };
2201
2202 PersistedFileWorkspace::save(&abs_path, file_state);
2203 tracing::debug!("Saved file state on close for {:?}", abs_path);
2204 }
2205
2206 pub(crate) fn take_pending_semantic_token_request(
2208 &mut self,
2209 request_id: u64,
2210 ) -> Option<crate::app::SemanticTokenFullRequest> {
2211 if let Some(request) = self.pending_semantic_token_requests.remove(&request_id) {
2212 self.semantic_tokens_in_flight.remove(&request.buffer_id);
2213 Some(request)
2214 } else {
2215 None
2216 }
2217 }
2218
2219 pub(crate) fn take_pending_semantic_token_range_request(
2221 &mut self,
2222 request_id: u64,
2223 ) -> Option<crate::app::SemanticTokenRangeRequest> {
2224 if let Some(request) = self
2225 .pending_semantic_token_range_requests
2226 .remove(&request_id)
2227 {
2228 self.semantic_tokens_range_in_flight
2229 .remove(&request.buffer_id);
2230 Some(request)
2231 } else {
2232 None
2233 }
2234 }
2235
2236 pub fn move_cursor_to_visible_area(&mut self, split_id: LeafId, buffer_id: BufferId) {
2239 let (top_byte, viewport_height) =
2240 if let Some(view_state) = self.buffers.splits().and_then(|(_, vs)| vs.get(&split_id)) {
2241 (
2242 view_state.viewport.top_byte,
2243 view_state.viewport.height as usize,
2244 )
2245 } else {
2246 return;
2247 };
2248
2249 if let Some(state) = self.buffers.get_mut(&buffer_id) {
2250 let buffer_len = state.buffer.len();
2251
2252 let mut iter = state.buffer.line_iterator(top_byte, 80);
2253 let mut bottom_byte = buffer_len;
2254
2255 for _ in 0..viewport_height {
2256 if let Some((pos, line)) = iter.next_line() {
2257 bottom_byte = pos + line.len();
2258 } else {
2259 bottom_byte = buffer_len;
2260 break;
2261 }
2262 }
2263
2264 if let Some(view_state) = self
2265 .split_view_states_mut()
2266 .and_then(|vs| vs.get_mut(&split_id))
2267 {
2268 let cursor_pos = view_state.cursors.primary().position;
2269 if cursor_pos < top_byte || cursor_pos > bottom_byte {
2270 let cursor = view_state.cursors.primary_mut();
2271 cursor.position = top_byte;
2272 }
2275 }
2276 }
2277 }
2278
2279 pub fn calculate_max_scroll_position(
2284 buffer: &mut crate::model::buffer::Buffer,
2285 viewport_height: usize,
2286 ) -> usize {
2287 if viewport_height == 0 {
2288 return 0;
2289 }
2290
2291 let buffer_len = buffer.len();
2292 if buffer_len == 0 {
2293 return 0;
2294 }
2295
2296 let mut line_count = 0;
2297 let mut iter = buffer.line_iterator(0, 80);
2298 while iter.next_line().is_some() {
2299 line_count += 1;
2300 }
2301
2302 if line_count <= viewport_height {
2303 return 0;
2304 }
2305
2306 let scrollable_lines = line_count.saturating_sub(viewport_height);
2307
2308 let mut iter = buffer.line_iterator(0, 80);
2309 let mut current_line = 0;
2310 let mut max_byte_pos = 0;
2311
2312 while current_line < scrollable_lines {
2313 if let Some((pos, _content)) = iter.next_line() {
2314 max_byte_pos = pos;
2315 current_line += 1;
2316 } else {
2317 break;
2318 }
2319 }
2320
2321 max_byte_pos
2322 }
2323
2324 pub fn split_at_position(&self, col: u16, row: u16) -> Option<(LeafId, BufferId)> {
2329 for &(split_id, buffer_id, content_rect, scrollbar_rect, _, _) in
2330 &self.layout_cache.split_areas
2331 {
2332 let in_content = col >= content_rect.x
2333 && col < content_rect.x + content_rect.width
2334 && row >= content_rect.y
2335 && row < content_rect.y + content_rect.height;
2336 let in_scrollbar = scrollbar_rect.width > 0
2337 && scrollbar_rect.height > 0
2338 && col >= scrollbar_rect.x
2339 && col < scrollbar_rect.x + scrollbar_rect.width
2340 && row >= scrollbar_rect.y
2341 && row < scrollbar_rect.y + scrollbar_rect.height;
2342 if in_content || in_scrollbar {
2343 return Some((split_id, buffer_id));
2344 }
2345 }
2346 None
2347 }
2348
2349 pub fn check_diagnostic_pull_timer(&mut self) -> bool {
2354 let Some((buffer_id, trigger_time)) = self.scheduled_diagnostic_pull else {
2355 return false;
2356 };
2357
2358 if std::time::Instant::now() < trigger_time {
2359 return false;
2360 }
2361
2362 self.scheduled_diagnostic_pull = None;
2363
2364 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
2365 return false;
2366 };
2367 let Some(uri) = metadata.file_uri().cloned() else {
2368 return false;
2369 };
2370 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
2371 return false;
2372 };
2373
2374 let previous_result_id = self.diagnostic_result_ids.get(uri.as_str()).cloned();
2375 let request_id = self.next_lsp_request_id;
2376 self.next_lsp_request_id += 1;
2377
2378 let Some(lsp) = self.lsp.as_mut() else {
2379 return false;
2380 };
2381 let Some(sh) = lsp.handle_for_feature_mut(&language, crate::types::LspFeature::Diagnostics)
2382 else {
2383 return false;
2384 };
2385 if let Err(e) =
2386 sh.handle
2387 .document_diagnostic(request_id, uri.as_uri().clone(), previous_result_id)
2388 {
2389 tracing::debug!(
2390 "Failed to pull diagnostics after edit for {}: {}",
2391 uri.as_str(),
2392 e
2393 );
2394 } else {
2395 tracing::debug!(
2396 "Pulling diagnostics after edit for {} (request_id={})",
2397 uri.as_str(),
2398 request_id
2399 );
2400 }
2401
2402 false
2403 }
2404
2405 pub fn open_local_file(&mut self, path: &std::path::Path) -> anyhow::Result<BufferId> {
2413 let resolved_path = if path.is_relative() {
2415 self.root.join(path)
2416 } else {
2417 path.to_path_buf()
2418 };
2419
2420 let display_path = resolved_path.clone();
2422
2423 let canonical_path = resolved_path
2425 .canonicalize()
2426 .unwrap_or_else(|_| resolved_path.clone());
2427 let path = canonical_path.as_path();
2428
2429 let already_open = self
2431 .buffers
2432 .iter()
2433 .find(|(_, state)| state.buffer.file_path() == Some(path))
2434 .map(|(id, _)| *id);
2435
2436 if let Some(id) = already_open {
2437 self.set_active_buffer(id);
2438 return Ok(id);
2439 }
2440
2441 let buffer_id = self.alloc_buffer_id();
2443
2444 let buffer = crate::model::buffer::Buffer::load_from_file(
2447 &canonical_path,
2448 self.config().editor.large_file_threshold_bytes as usize,
2449 std::sync::Arc::clone(&self.resources.local_filesystem),
2450 )?;
2451 let first_line = buffer.first_line_lossy();
2452 let detected =
2453 crate::primitives::detected_language::DetectedLanguage::from_path_with_fallback(
2454 &display_path,
2455 first_line.as_deref(),
2456 &self.resources.grammar_registry,
2457 &self.config().languages,
2458 self.config().default_language.as_deref(),
2459 );
2460 let state = crate::state::EditorState::from_buffer_with_language(buffer, detected);
2461
2462 self.buffers.insert(buffer_id, state);
2463 self.event_logs
2464 .insert(buffer_id, crate::model::event::EventLog::new());
2465
2466 let metadata = crate::app::types::BufferMetadata::with_file(
2468 path.to_path_buf(),
2469 &display_path,
2470 &self.root,
2471 self.authority().path_translation.as_ref(),
2472 );
2473 self.buffer_metadata.insert(buffer_id, metadata);
2474
2475 let target_split = self.preferred_split_for_file();
2477 let line_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
2478 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
2479 let cfg = self.config().editor.clone();
2482 if let Some(view_state) = self
2483 .split_view_states_mut()
2484 .expect("active window must have a populated split layout")
2485 .get_mut(&target_split)
2486 {
2487 view_state.add_buffer(buffer_id);
2488 let buf_state = view_state.ensure_buffer_state(buffer_id);
2489 buf_state.apply_config_defaults(
2490 cfg.line_numbers,
2491 cfg.highlight_current_line,
2492 line_wrap,
2493 cfg.wrap_indent,
2494 wrap_column,
2495 cfg.rulers,
2496 );
2497 }
2498
2499 self.set_active_buffer(buffer_id);
2500
2501 let display_name = path.display().to_string();
2502 self.set_status_message(rust_i18n::t!("buffer.opened", name = display_name).to_string());
2503
2504 Ok(buffer_id)
2505 }
2506
2507 pub fn mark_buffer_read_only(&mut self, buffer_id: BufferId, read_only: bool) {
2511 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
2512 metadata.read_only = read_only;
2513 }
2514 if let Some(state) = self.buffers.get_mut(&buffer_id) {
2515 state.editing_disabled = read_only;
2516 }
2517 }
2518
2519 pub fn clear_warnings(&mut self) {
2522 self.warning_domains.general.clear();
2523 self.warning_domains.lsp.clear();
2524 self.set_status_message("Warnings cleared".to_string());
2525 }
2526
2527 pub fn update_lsp_warning_domain(&mut self) {
2531 let statuses = self.lsp_server_statuses.clone();
2533 self.warning_domains.lsp.update_from_statuses(&statuses);
2534 }
2535
2536 pub fn check_semantic_highlight_timer(&self) -> bool {
2541 self.buffers.any_needs_semantic_redraw()
2542 }
2543
2544 pub fn search_match_at_primary_cursor(&self) -> Option<std::ops::Range<usize>> {
2550 let search_state = self.search_state.as_ref()?;
2551 let pos = self.active_cursors().primary().position;
2552 let idx = match search_state.matches.binary_search(&pos) {
2553 Ok(i) => i,
2554 Err(0) => return None,
2555 Err(i) => i - 1,
2556 };
2557 let start = search_state.matches[idx];
2558 let len = *search_state.match_lengths.get(idx)?;
2559 if pos < start + len {
2560 Some(start..start + len)
2561 } else {
2562 None
2563 }
2564 }
2565
2566 pub fn update_search_highlights(
2570 &mut self,
2571 query: &str,
2572 search_fg: ratatui::style::Color,
2573 search_bg: ratatui::style::Color,
2574 ) {
2575 if query.is_empty() {
2576 self.clear_search_highlights();
2577 return;
2578 }
2579
2580 let case_sensitive = self.search_case_sensitive;
2581 let whole_word = self.search_whole_word;
2582 let use_regex = self.search_use_regex;
2583 let ns = self.search_namespace.clone();
2584
2585 let regex_pattern = if use_regex {
2586 if whole_word {
2587 format!(r"\b{}\b", query)
2588 } else {
2589 query.to_string()
2590 }
2591 } else {
2592 let escaped = regex::escape(query);
2593 if whole_word {
2594 format!(r"\b{}\b", escaped)
2595 } else {
2596 escaped
2597 }
2598 };
2599
2600 let regex = regex::RegexBuilder::new(®ex_pattern)
2601 .case_insensitive(!case_sensitive)
2602 .build();
2603 let regex = match regex {
2604 Ok(r) => r,
2605 Err(_) => {
2606 self.clear_search_highlights();
2607 return;
2608 }
2609 };
2610
2611 let active_split = self.effective_active_split();
2612 let (top_byte, visible_height) = self
2613 .buffers
2614 .splits()
2615 .expect("active window must have a populated split layout")
2616 .1
2617 .get(&active_split)
2618 .map(|vs| (vs.viewport.top_byte, vs.viewport.height.saturating_sub(2)))
2619 .unwrap_or((0, 20));
2620
2621 let state = self.active_state_mut();
2622 state.overlays.clear_namespace(&ns, &mut state.marker_list);
2623
2624 let visible_start = top_byte;
2625 let mut visible_end = top_byte;
2626 {
2627 let mut line_iter = state.buffer.line_iterator(top_byte, 80);
2628 for _ in 0..visible_height {
2629 if let Some((line_start, line_content)) = line_iter.next_line() {
2630 visible_end = line_start + line_content.len();
2631 } else {
2632 break;
2633 }
2634 }
2635 }
2636 visible_end = visible_end.min(state.buffer.len());
2637 let visible_text = state.get_text_range(visible_start, visible_end);
2638
2639 for mat in regex.find_iter(&visible_text) {
2640 let absolute_pos = visible_start + mat.start();
2641 let match_len = mat.end() - mat.start();
2642 let search_style = ratatui::style::Style::default().fg(search_fg).bg(search_bg);
2643 let overlay = crate::view::overlay::Overlay::with_namespace_fixed_end(
2644 &mut state.marker_list,
2645 absolute_pos..(absolute_pos + match_len),
2646 crate::view::overlay::OverlayFace::Style {
2647 style: search_style,
2648 },
2649 ns.clone(),
2650 )
2651 .with_priority_value(10);
2652 state.overlays.add(overlay);
2653 }
2654 }
2655
2656 pub fn reevaluate_search_overlays_around(
2671 &mut self,
2672 edit_start: usize,
2673 edit_new_len: usize,
2674 search_fg: ratatui::style::Color,
2675 search_bg: ratatui::style::Color,
2676 ) {
2677 let query = match self.search_state.as_ref() {
2678 Some(ss) if !ss.query.is_empty() => ss.query.clone(),
2679 _ => return,
2680 };
2681
2682 let case_sensitive = self.search_case_sensitive;
2683 let whole_word = self.search_whole_word;
2684 let use_regex = self.search_use_regex;
2685 let ns = self.search_namespace.clone();
2686
2687 let regex_pattern = if use_regex {
2688 if whole_word {
2689 format!(r"\b{}\b", query)
2690 } else {
2691 query
2692 }
2693 } else {
2694 let escaped = regex::escape(&query);
2695 if whole_word {
2696 format!(r"\b{}\b", escaped)
2697 } else {
2698 escaped
2699 }
2700 };
2701
2702 let regex = match regex::RegexBuilder::new(®ex_pattern)
2703 .case_insensitive(!case_sensitive)
2704 .build()
2705 {
2706 Ok(r) => r,
2707 Err(_) => return,
2708 };
2709
2710 let state = self.active_state_mut();
2711 let buf_len = state.buffer.len();
2712 let edit_end = edit_start.saturating_add(edit_new_len).min(buf_len);
2713
2714 let start_line = state.buffer.get_line_number(edit_start.min(buf_len));
2717 let end_line = state.buffer.get_line_number(edit_end);
2718 let win_start = state.buffer.line_start_offset(start_line).unwrap_or(0);
2719 let win_end = state
2720 .buffer
2721 .line_start_offset(end_line + 1)
2722 .unwrap_or(buf_len)
2723 .min(buf_len);
2724
2725 let text = state.get_text_range(win_start, win_end);
2726
2727 let mut new_overlays = Vec::new();
2728 for mat in regex.find_iter(&text) {
2729 let absolute_pos = win_start + mat.start();
2730 let match_len = mat.end() - mat.start();
2731 let search_style = ratatui::style::Style::default().fg(search_fg).bg(search_bg);
2732 new_overlays.push(
2733 crate::view::overlay::Overlay::with_namespace_fixed_end(
2734 &mut state.marker_list,
2735 absolute_pos..(absolute_pos + match_len),
2736 crate::view::overlay::OverlayFace::Style {
2737 style: search_style,
2738 },
2739 ns.clone(),
2740 )
2741 .with_priority_value(10),
2742 );
2743 }
2744
2745 state.overlays.replace_range_in_namespace(
2746 &ns,
2747 &(win_start..win_end),
2748 new_overlays,
2749 &mut state.marker_list,
2750 );
2751 }
2752
2753 pub fn file_explorer_is_visible(&self) -> bool {
2757 self.file_explorer_visible && self.file_explorer.is_some()
2758 }
2759
2760 pub fn file_explorer_extend_selection_up(&mut self) {
2762 if let Some(explorer) = self.file_explorer.as_mut() {
2763 explorer.extend_selection_up();
2764 }
2765 }
2766
2767 pub fn file_explorer_extend_selection_down(&mut self) {
2769 if let Some(explorer) = self.file_explorer.as_mut() {
2770 explorer.extend_selection_down();
2771 }
2772 }
2773
2774 pub fn file_explorer_toggle_select(&mut self) {
2776 if let Some(explorer) = self.file_explorer.as_mut() {
2777 explorer.toggle_select();
2778 }
2779 }
2780
2781 pub fn file_explorer_select_all(&mut self) {
2783 if let Some(explorer) = self.file_explorer.as_mut() {
2784 explorer.select_all();
2785 }
2786 }
2787
2788 pub fn file_explorer_search_push_char(&mut self, c: char) {
2790 if let Some(explorer) = self.file_explorer.as_mut() {
2791 explorer.search_push_char(c);
2792 explorer.update_scroll_for_selection();
2793 }
2794 }
2795
2796 pub fn file_explorer_search_pop_char(&mut self) {
2798 if let Some(explorer) = self.file_explorer.as_mut() {
2799 explorer.search_pop_char();
2800 explorer.update_scroll_for_selection();
2801 }
2802 }
2803
2804 pub fn schedule_folding_ranges_refresh(&mut self, buffer_id: BufferId) {
2810 const FOLDING_RANGES_DEBOUNCE_MS: u64 = 300;
2811 let next_time = std::time::Instant::now()
2812 + std::time::Duration::from_millis(FOLDING_RANGES_DEBOUNCE_MS);
2813 self.folding_ranges_debounce.insert(buffer_id, next_time);
2814 }
2815
2816 pub fn schedule_semantic_tokens_full_refresh(&mut self, buffer_id: BufferId) {
2820 const SEMANTIC_TOKENS_FULL_DEBOUNCE_MS: u64 = 500;
2821 if !self.resources.config.editor.enable_semantic_tokens_full {
2822 return;
2823 }
2824 let next_time = std::time::Instant::now()
2825 + std::time::Duration::from_millis(SEMANTIC_TOKENS_FULL_DEBOUNCE_MS);
2826 self.semantic_tokens_full_debounce
2827 .insert(buffer_id, next_time);
2828 }
2829
2830 pub(crate) fn send_lsp_changes_for_buffer(
2840 &mut self,
2841 buffer_id: BufferId,
2842 changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
2843 ) {
2844 const INLAY_HINTS_DEBOUNCE_MS: u64 = 500;
2845
2846 if changes.is_empty() {
2847 return;
2848 }
2849
2850 let metadata = match self.buffer_metadata.get(&buffer_id) {
2851 Some(m) => m,
2852 None => {
2853 tracing::debug!(
2854 "send_lsp_changes_for_buffer: no metadata for buffer {:?}",
2855 buffer_id
2856 );
2857 return;
2858 }
2859 };
2860
2861 if !metadata.lsp_enabled {
2862 tracing::debug!("send_lsp_changes_for_buffer: LSP disabled for this buffer");
2863 return;
2864 }
2865
2866 let uri = match metadata.file_uri() {
2867 Some(u) => u.clone(),
2868 None => {
2869 tracing::debug!(
2870 "send_lsp_changes_for_buffer: no URI for buffer (not a file or URI creation failed)"
2871 );
2872 return;
2873 }
2874 };
2875 let file_path = metadata.file_path().cloned();
2876
2877 let language = match self.buffers.get(&buffer_id).map(|s| s.language.clone()) {
2878 Some(l) => l,
2879 None => {
2880 tracing::debug!(
2881 "send_lsp_changes_for_buffer: no buffer state for {:?}",
2882 buffer_id
2883 );
2884 return;
2885 }
2886 };
2887
2888 tracing::trace!(
2889 "send_lsp_changes_for_buffer: sending {} changes to {} in single didChange notification",
2890 changes.len(),
2891 uri.as_str()
2892 );
2893
2894 use crate::services::lsp::manager::LspSpawnResult;
2895 let Some(lsp) = self.lsp.as_mut() else {
2896 tracing::debug!("send_lsp_changes_for_buffer: no LSP manager available");
2897 return;
2898 };
2899
2900 if lsp.try_spawn(&language, file_path.as_deref()) != LspSpawnResult::Spawned {
2901 tracing::debug!(
2902 "send_lsp_changes_for_buffer: LSP not running for {} (auto_start disabled)",
2903 language
2904 );
2905 return;
2906 }
2907
2908 let handles_needing_open: Vec<_> = {
2909 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
2910 return;
2911 };
2912 lsp.get_handles(&language)
2913 .into_iter()
2914 .filter(|sh| !metadata.lsp_opened_with.contains(&sh.handle.id()))
2915 .map(|sh| (sh.name.clone(), sh.handle.id()))
2916 .collect()
2917 };
2918
2919 if !handles_needing_open.is_empty() {
2920 let text = match self
2921 .buffers
2922 .get(&buffer_id)
2923 .and_then(|s| s.buffer.to_string())
2924 {
2925 Some(t) => t,
2926 None => {
2927 tracing::debug!(
2928 "send_lsp_changes_for_buffer: buffer text not available for didOpen"
2929 );
2930 return;
2931 }
2932 };
2933
2934 let Some(lsp) = self.lsp.as_mut() else {
2935 return;
2936 };
2937 for sh in lsp.get_handles_mut(&language) {
2938 if handles_needing_open
2939 .iter()
2940 .any(|(_, id)| *id == sh.handle.id())
2941 {
2942 if let Err(e) =
2943 sh.handle
2944 .did_open(uri.as_uri().clone(), text.clone(), language.clone())
2945 {
2946 tracing::warn!(
2947 "Failed to send didOpen to '{}' before didChange: {}",
2948 sh.name,
2949 e
2950 );
2951 } else {
2952 tracing::debug!(
2953 "Sent didOpen for {} to LSP handle '{}' before didChange",
2954 uri.as_str(),
2955 sh.name
2956 );
2957 }
2958 }
2959 }
2960
2961 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
2962 for (_, handle_id) in &handles_needing_open {
2963 metadata.lsp_opened_with.insert(*handle_id);
2964 }
2965 }
2966
2967 return;
2971 }
2972
2973 let Some(lsp) = self.lsp.as_mut() else {
2974 return;
2975 };
2976 let mut any_sent = false;
2977 for sh in lsp.get_handles_mut(&language) {
2978 if let Err(e) = sh.handle.did_change(uri.as_uri().clone(), changes.clone()) {
2979 tracing::warn!("Failed to send didChange to '{}': {}", sh.name, e);
2980 } else {
2981 any_sent = true;
2982 }
2983 }
2984 if any_sent {
2985 tracing::trace!("Successfully sent batched didChange to LSP");
2986
2987 if let Some(state) = self.buffers.get(&buffer_id) {
2988 if let Some(path) = state.buffer.file_path() {
2989 crate::services::lsp::diagnostics::invalidate_cache_for_file(
2990 &path.to_string_lossy(),
2991 );
2992 }
2993 }
2994
2995 self.scheduled_diagnostic_pull = Some((
2996 buffer_id,
2997 std::time::Instant::now() + std::time::Duration::from_millis(1000),
2998 ));
2999
3000 if self.resources.config.editor.enable_inlay_hints {
3001 self.scheduled_inlay_hints_request = Some((
3002 buffer_id,
3003 std::time::Instant::now()
3004 + std::time::Duration::from_millis(INLAY_HINTS_DEBOUNCE_MS),
3005 ));
3006 }
3007 }
3008 }
3009
3010 pub fn invalidate_layouts_for_buffer(&mut self, buffer_id: BufferId) {
3014 let Some((mgr, vs_map)) = self.buffers.splits_mut() else {
3015 return;
3016 };
3017 let splits_for_buffer = mgr.splits_for_buffer(buffer_id);
3018 for split_id in splits_for_buffer {
3019 if let Some(view_state) = vs_map.get_mut(&split_id) {
3020 view_state.invalidate_layout();
3021 view_state.view_transform = None;
3022 view_state.view_transform_stale = true;
3023 }
3024 }
3025 }
3026
3027 pub fn adjust_other_split_cursors_for_event(&mut self, event: &Event) {
3034 let current_buffer_id = self.active_buffer();
3035 let buffer_len = self
3036 .buffers
3037 .get(¤t_buffer_id)
3038 .map(|s| s.buffer.len())
3039 .unwrap_or(0);
3040 let Some((mgr, vs_map)) = self.buffers.splits_mut() else {
3041 return;
3042 };
3043 let current_split_id = mgr.active_split();
3044 let splits_for_buffer = mgr.splits_for_buffer(current_buffer_id);
3045
3046 if let Event::BulkEdit { new_cursors, .. } = event {
3047 for split_id in splits_for_buffer {
3048 if split_id == current_split_id {
3049 continue;
3050 }
3051 if let Some(view_state) = vs_map.get_mut(&split_id) {
3052 if let Some((_, pos, _)) = new_cursors.first() {
3053 let new_pos = (*pos).min(buffer_len);
3054 view_state.cursors.primary_mut().position = new_pos;
3055 view_state.cursors.primary_mut().anchor = None;
3056 }
3057 }
3058 }
3059 return;
3060 }
3061
3062 let adjustments: Vec<(usize, usize, usize)> = match event {
3063 Event::Insert { position, text, .. } => {
3064 vec![(*position, 0, text.len())]
3065 }
3066 Event::Delete { range, .. } => {
3067 vec![(range.start, range.len(), 0)]
3068 }
3069 Event::Batch { events, .. } => events
3070 .iter()
3071 .filter_map(|e| match e {
3072 Event::Insert { position, text, .. } => Some((*position, 0, text.len())),
3073 Event::Delete { range, .. } => Some((range.start, range.len(), 0)),
3074 _ => None,
3075 })
3076 .collect(),
3077 _ => Vec::new(),
3078 };
3079
3080 if adjustments.is_empty() {
3081 return;
3082 }
3083
3084 for split_id in splits_for_buffer {
3085 if split_id == current_split_id {
3086 continue;
3087 }
3088 if let Some(view_state) = vs_map.get_mut(&split_id) {
3089 for (edit_pos, old_len, new_len) in &adjustments {
3090 view_state
3091 .cursors
3092 .adjust_for_edit(*edit_pos, *old_len, *new_len);
3093 }
3094 }
3095 }
3096 }
3097
3098 pub(crate) fn handle_scroll_event(&mut self, line_offset: isize) {
3104 use crate::view::ui::view_pipeline::ViewLineIterator;
3105
3106 let Some((mgr, _)) = self.buffers.splits() else {
3107 return;
3108 };
3109 let active_split = mgr.active_split();
3110
3111 if let Some(group) = self
3112 .scroll_sync_manager
3113 .find_group_for_split(active_split.into())
3114 {
3115 let left = group.left_split;
3116 let right = group.right_split;
3117 if let Some(vs_map) = self.split_view_states_mut() {
3118 if let Some(vs) = vs_map.get_mut(&LeafId(left)) {
3119 vs.viewport.set_skip_ensure_visible();
3120 }
3121 if let Some(vs) = vs_map.get_mut(&LeafId(right)) {
3122 vs.viewport.set_skip_ensure_visible();
3123 }
3124 }
3125 }
3126
3127 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
3128 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
3129 let splits_to_scroll = if let Some(group_id) = sync_group {
3130 mgr.get_splits_in_group(group_id, vs_map)
3131 } else {
3132 vec![active_split]
3133 };
3134
3135 let tab_size = self.resources.config.editor.tab_size;
3136 for split_id in splits_to_scroll {
3137 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
3138 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
3139 continue;
3140 };
3141
3142 let view_transform_tokens = vs_map
3143 .get(&split_id)
3144 .and_then(|vs| vs.view_transform.as_ref())
3145 .map(|vt| vt.tokens.clone());
3146
3147 self.buffers
3148 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
3149 let soft_breaks = state.collect_soft_break_positions();
3150 let virtual_lines = state.collect_virtual_line_positions();
3151 let buffer = &mut state.buffer;
3152 if let Some(tokens) = view_transform_tokens {
3153 let view_lines: Vec<_> =
3154 ViewLineIterator::new(&tokens, false, false, tab_size, false).collect();
3155 view_state
3156 .viewport
3157 .scroll_view_lines(&view_lines, line_offset);
3158 } else if line_offset > 0 {
3159 view_state.viewport.scroll_down(
3160 buffer,
3161 &soft_breaks,
3162 &virtual_lines,
3163 line_offset as usize,
3164 );
3165 } else {
3166 view_state.viewport.scroll_up(
3167 buffer,
3168 &soft_breaks,
3169 &virtual_lines,
3170 line_offset.unsigned_abs(),
3171 );
3172 }
3173 view_state.viewport.set_skip_ensure_visible();
3174 });
3175 }
3176 }
3177
3178 pub(crate) fn handle_set_viewport_event(&mut self, top_line: usize) {
3180 let Some((mgr, _)) = self.buffers.splits() else {
3181 return;
3182 };
3183 let active_split = mgr.active_split();
3184
3185 if self
3186 .scroll_sync_manager
3187 .is_split_synced(active_split.into())
3188 {
3189 if let Some(group) = self
3190 .scroll_sync_manager
3191 .find_group_for_split_mut(active_split.into())
3192 {
3193 let scroll_line = if group.is_left_split(active_split.into()) {
3194 top_line
3195 } else {
3196 group.right_to_left_line(top_line)
3197 };
3198 group.set_scroll_line(scroll_line);
3199 }
3200
3201 let (left, right) = match self
3202 .scroll_sync_manager
3203 .find_group_for_split(active_split.into())
3204 {
3205 Some(group) => (group.left_split, group.right_split),
3206 None => return,
3207 };
3208 if let Some(vs_map) = self.split_view_states_mut() {
3209 if let Some(vs) = vs_map.get_mut(&LeafId(left)) {
3210 vs.viewport.set_skip_ensure_visible();
3211 }
3212 if let Some(vs) = vs_map.get_mut(&LeafId(right)) {
3213 vs.viewport.set_skip_ensure_visible();
3214 }
3215 }
3216 return;
3217 }
3218
3219 let (mgr, vs_map) = self.buffers.splits().expect("splits checked above");
3220 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
3221 let splits_to_scroll = if let Some(group_id) = sync_group {
3222 mgr.get_splits_in_group(group_id, vs_map)
3223 } else {
3224 vec![active_split]
3225 };
3226
3227 for split_id in splits_to_scroll {
3228 let (mgr, _) = self.buffers.splits().expect("splits checked above");
3229 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
3230 continue;
3231 };
3232
3233 self.buffers
3234 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
3235 view_state.viewport.scroll_to(&mut state.buffer, top_line);
3236 view_state.viewport.set_skip_ensure_visible();
3237 });
3238 }
3239 }
3240
3241 pub(crate) fn handle_recenter_event(&mut self) {
3243 let Some((mgr, vs_map)) = self.buffers.splits() else {
3244 return;
3245 };
3246 let active_split = mgr.active_split();
3247
3248 let sync_group = vs_map.get(&active_split).and_then(|vs| vs.sync_group);
3249 let splits_to_recenter = if let Some(group_id) = sync_group {
3250 mgr.get_splits_in_group(group_id, vs_map)
3251 } else {
3252 vec![active_split]
3253 };
3254
3255 for split_id in splits_to_recenter {
3256 let (mgr, _) = self.buffers.splits().expect("splits checked above");
3257 let Some(buffer_id) = mgr.buffer_for_split(split_id) else {
3258 continue;
3259 };
3260
3261 self.buffers
3262 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
3263 let buffer = &mut state.buffer;
3264 let cursor_pos = view_state.cursors.primary().position;
3265 view_state.viewport.center_on_position(buffer, cursor_pos);
3270 view_state.viewport.set_skip_ensure_visible();
3271 });
3272 }
3273 }
3274
3275 pub fn set_pane_buffer(&mut self, leaf: LeafId, buffer_id: BufferId) {
3288 let (mgr, vs_map) = self
3289 .buffers
3290 .splits_mut()
3291 .expect("active window must have a populated split layout");
3292 mgr.set_split_buffer(leaf, buffer_id);
3293 if let Some(view_state) = vs_map.get_mut(&leaf) {
3294 view_state.switch_buffer(buffer_id);
3295 }
3296 }
3297}
3298
3299