1use std::path::PathBuf;
8use std::sync::{Arc, RwLock};
9
10use rust_i18n::t;
11
12use crate::input::command_registry::CommandRegistry;
13use crate::input::commands::Suggestion;
14use crate::input::keybindings::KeyContext;
15use crate::input::quick_open::{BufferInfo, QuickOpenContext};
16use crate::services::async_bridge::AsyncMessage;
17use crate::services::plugins::PluginManager;
18use crate::view::prompt::{Prompt, PromptType};
19
20use super::file_open;
21use super::Editor;
22
23impl Editor {
24 pub fn start_prompt(&mut self, message: String, prompt_type: PromptType) {
28 self.start_prompt_with_suggestions(message, prompt_type, Vec::new());
29 }
30
31 pub(super) fn start_search_prompt(
36 &mut self,
37 message: String,
38 prompt_type: PromptType,
39 use_selection_range: bool,
40 ) {
41 self.pending_search_range = None;
43
44 let selection_range = self.active_cursors().primary().selection_range();
45
46 let selected_text = if let Some(range) = selection_range.clone() {
47 let state = self.active_state_mut();
48 let text = state.get_text_range(range.start, range.end);
49 if !text.contains('\n') && !text.is_empty() {
50 Some(text)
51 } else {
52 None
53 }
54 } else {
55 None
56 };
57
58 if use_selection_range {
59 self.pending_search_range = selection_range;
60 }
61
62 let from_history = selected_text.is_none();
64 let default_text = selected_text.or_else(|| {
65 self.get_prompt_history("search")
66 .and_then(|h| h.last().map(|s| s.to_string()))
67 });
68
69 self.start_prompt(message, prompt_type);
71
72 if let Some(text) = default_text {
74 if let Some(ref mut prompt) = self.prompt {
75 prompt.set_input(text.clone());
76 prompt.selection_anchor = Some(0);
77 prompt.cursor_pos = text.len();
78 }
79 if from_history {
80 self.get_or_create_prompt_history("search").init_at_last();
81 }
82 self.update_search_highlights(&text);
83 }
84 }
85
86 pub fn start_prompt_with_suggestions(
88 &mut self,
89 message: String,
90 prompt_type: PromptType,
91 suggestions: Vec<Suggestion>,
92 ) {
93 self.on_editor_focus_lost();
95
96 match prompt_type {
99 PromptType::Search | PromptType::ReplaceSearch | PromptType::QueryReplaceSearch => {
100 self.clear_search_highlights();
101 }
102 _ => {}
103 }
104
105 let needs_suggestions = matches!(
107 prompt_type,
108 PromptType::OpenFile | PromptType::SwitchProject | PromptType::SaveFileAs
109 );
110
111 self.prompt = Some(Prompt::with_suggestions(message, prompt_type, suggestions));
112
113 if needs_suggestions {
115 self.update_prompt_suggestions();
116 }
117 }
118
119 pub fn start_prompt_with_initial_text(
121 &mut self,
122 message: String,
123 prompt_type: PromptType,
124 initial_text: String,
125 ) {
126 self.on_editor_focus_lost();
128
129 self.prompt = Some(Prompt::with_initial_text(
130 message,
131 prompt_type,
132 initial_text,
133 ));
134 }
135
136 pub fn start_quick_open(&mut self) {
138 self.start_quick_open_with_prefix(">");
139 }
140
141 pub fn start_quick_open_with_prefix(&mut self, prefix: &str) {
143 self.on_editor_focus_lost();
144 self.status_message = None;
145 self.goto_line_preview = None;
146
147 let mut prompt = Prompt::with_suggestions(String::new(), PromptType::QuickOpen, vec![]);
148 prompt.input = prefix.to_string();
149 prompt.cursor_pos = prefix.len();
150 self.prompt = Some(prompt);
151
152 self.update_quick_open_suggestions(prefix);
153 }
154
155 pub(super) fn build_quick_open_context(&self) -> QuickOpenContext {
157 let open_buffers = self
158 .buffers
159 .iter()
160 .filter_map(|(buffer_id, state)| {
161 let path = state.buffer.file_path()?;
162 let name = path
163 .file_name()
164 .map(|n| n.to_string_lossy().to_string())
165 .unwrap_or_else(|| format!("Buffer {}", buffer_id.0));
166 Some(BufferInfo {
167 id: buffer_id.0,
168 path: path.display().to_string(),
169 name,
170 modified: state.buffer.is_modified(),
171 })
172 })
173 .collect();
174
175 let has_lsp_config = {
176 let language = self
177 .buffers
178 .get(&self.active_buffer())
179 .map(|s| s.language.as_str());
180 language
181 .and_then(|lang| self.lsp.as_ref().and_then(|lsp| lsp.get_config(lang)))
182 .is_some()
183 };
184
185 QuickOpenContext {
186 cwd: self.working_dir.display().to_string(),
187 open_buffers,
188 active_buffer_id: self.active_buffer().0,
189 active_buffer_path: self
190 .active_state()
191 .buffer
192 .file_path()
193 .map(|p| p.display().to_string()),
194 has_selection: self.has_active_selection(),
195 key_context: self.key_context.clone(),
196 custom_contexts: self.active_custom_contexts.clone(),
197 buffer_mode: self
198 .buffer_metadata
199 .get(&self.active_buffer())
200 .and_then(|m| m.virtual_mode())
201 .map(|s| s.to_string()),
202 has_lsp_config,
203 relative_line_numbers: self.config.editor.relative_line_numbers,
204 }
205 }
206
207 pub(super) fn update_quick_open_suggestions(&mut self, input: &str) {
209 let context = self.build_quick_open_context();
210 let suggestions = if let Some((provider, query)) =
211 self.quick_open_registry.get_provider_for_input(input)
212 {
213 provider.suggestions(query, &context)
214 } else {
215 vec![]
216 };
217
218 if let Some(prompt) = &mut self.prompt {
219 prompt.suggestions = suggestions;
220 prompt.selected_suggestion = if prompt.suggestions.is_empty() {
221 None
222 } else {
223 Some(0)
224 };
225 }
226
227 let input = input.trim();
236 let is_relative_syntax = input == ":" || input.starts_with(":-") || input.starts_with(":+");
237 let is_relative_config = self.config.editor.relative_line_numbers;
238 let target = if is_relative_syntax || is_relative_config {
239 None
240 } else {
241 Self::parse_quick_open_goto_line_target(input)
242 };
243 self.apply_goto_line_preview(target);
244 }
245
246 pub(super) fn parse_quick_open_goto_line_target(input: &str) -> Option<usize> {
248 input
249 .strip_prefix(':')
250 .and_then(|rest| rest.trim().parse::<usize>().ok())
251 .filter(|&n| n > 0)
252 }
253
254 pub(super) fn apply_goto_line_preview(&mut self, target_line: Option<usize>) {
261 if let Some(line) = target_line {
262 self.save_goto_line_preview_snapshot();
263 self.goto_line_col(line, None);
264 let new_position = self.active_cursors().primary().position;
267 if let Some(snap) = self.goto_line_preview.as_mut() {
268 snap.last_jump_position = new_position;
269 }
270 } else {
271 self.restore_goto_line_preview_snapshot();
272 }
273 }
274
275 pub(super) fn save_goto_line_preview_snapshot(&mut self) {
279 if self.goto_line_preview.is_some() {
280 return;
281 }
282
283 let buffer_id = self.active_buffer();
284 let split_id = self.split_manager.active_split();
285 let (cursor_id, position, anchor, sticky_column) = {
286 let cursors = self.active_cursors();
287 let primary = cursors.primary();
288 (
289 cursors.primary_id(),
290 primary.position,
291 primary.anchor,
292 primary.sticky_column,
293 )
294 };
295 let (viewport_top_byte, viewport_top_view_line_offset, viewport_left_column) = {
296 let vp = self.active_viewport();
297 (vp.top_byte, vp.top_view_line_offset, vp.left_column)
298 };
299
300 self.goto_line_preview = Some(super::GotoLinePreviewSnapshot {
301 buffer_id,
302 split_id,
303 cursor_id,
304 position,
305 anchor,
306 sticky_column,
307 viewport_top_byte,
308 viewport_top_view_line_offset,
309 viewport_left_column,
310 last_jump_position: position,
314 });
315 }
316
317 pub(super) fn restore_goto_line_preview_snapshot(&mut self) {
326 let Some(snap) = self.goto_line_preview.take() else {
327 return;
328 };
329
330 if self.active_buffer() != snap.buffer_id
333 || self.split_manager.active_split() != snap.split_id
334 {
335 return;
336 }
337
338 let cursors = self.active_cursors();
339 let current = cursors.primary();
340
341 if current.position != snap.last_jump_position {
345 return;
346 }
347 let event = crate::model::event::Event::MoveCursor {
348 cursor_id: snap.cursor_id,
349 old_position: current.position,
350 new_position: snap.position,
351 old_anchor: current.anchor,
352 new_anchor: snap.anchor,
353 old_sticky_column: current.sticky_column,
354 new_sticky_column: snap.sticky_column,
355 };
356
357 let state = self.buffers.get_mut(&snap.buffer_id).unwrap();
358 let view_state = self.split_view_states.get_mut(&snap.split_id).unwrap();
359 state.apply(&mut view_state.cursors, &event);
360
361 let vp = &mut view_state.viewport;
362 vp.top_byte = snap.viewport_top_byte;
363 vp.top_view_line_offset = snap.viewport_top_view_line_offset;
364 vp.left_column = snap.viewport_left_column;
365 vp.set_skip_ensure_visible();
368 }
369
370 pub(super) fn cancel_search_prompt_if_active(&mut self) {
373 if let Some(ref prompt) = self.prompt {
374 if matches!(
375 prompt.prompt_type,
376 PromptType::Search
377 | PromptType::ReplaceSearch
378 | PromptType::Replace { .. }
379 | PromptType::QueryReplaceSearch
380 | PromptType::QueryReplace { .. }
381 | PromptType::QueryReplaceConfirm
382 ) {
383 self.prompt = None;
384 self.interactive_replace_state = None;
386 let ns = self.search_namespace.clone();
388 let state = self.active_state_mut();
389 state.overlays.clear_namespace(&ns, &mut state.marker_list);
390 }
391 }
392 }
393
394 pub(super) fn prefill_open_file_prompt(&mut self) {
396 if let Some(prompt) = self.prompt.as_mut() {
400 if prompt.prompt_type == PromptType::OpenFile {
401 prompt.input.clear();
402 prompt.cursor_pos = 0;
403 prompt.selection_anchor = None;
404 }
405 }
406 }
407
408 pub(super) fn init_file_open_state(&mut self) {
414 let buffer_id = self.active_buffer();
416
417 let initial_dir = if self.is_terminal_buffer(buffer_id) {
420 self.get_terminal_id(buffer_id)
421 .and_then(|tid| self.terminal_manager.get(tid))
422 .and_then(|handle| handle.cwd())
423 .unwrap_or_else(|| self.working_dir.clone())
424 } else {
425 self.active_state()
426 .buffer
427 .file_path()
428 .and_then(|path| path.parent())
429 .map(|p| p.to_path_buf())
430 .unwrap_or_else(|| self.working_dir.clone())
431 };
432
433 let show_hidden = self.config.file_browser.show_hidden;
435 self.file_open_state = Some(file_open::FileOpenState::new(
436 initial_dir.clone(),
437 show_hidden,
438 self.authority.filesystem.clone(),
439 ));
440
441 self.load_file_open_directory(initial_dir);
443 self.load_file_open_shortcuts_async();
444 }
445
446 pub(super) fn init_folder_open_state(&mut self) {
451 let initial_dir = self.working_dir.clone();
453
454 let show_hidden = self.config.file_browser.show_hidden;
456 self.file_open_state = Some(file_open::FileOpenState::new(
457 initial_dir.clone(),
458 show_hidden,
459 self.authority.filesystem.clone(),
460 ));
461
462 self.load_file_open_directory(initial_dir);
464 self.load_file_open_shortcuts_async();
465 }
466
467 pub fn change_working_dir(&mut self, new_path: PathBuf) {
477 let new_path = new_path.canonicalize().unwrap_or(new_path);
479
480 self.request_restart(new_path);
483 }
484
485 pub(super) fn load_file_open_directory(&mut self, path: PathBuf) {
487 if let Some(state) = &mut self.file_open_state {
489 state.current_dir = path.clone();
490 state.loading = true;
491 state.error = None;
492 state.update_shortcuts();
493 }
494
495 if let Some(ref runtime) = self.tokio_runtime {
497 let fs_manager = self.fs_manager.clone();
498 let sender = self.async_bridge.as_ref().map(|b| b.sender());
499
500 runtime.spawn(async move {
501 let result = fs_manager.list_dir_with_metadata(path).await;
502 if let Some(sender) = sender {
503 #[allow(clippy::let_underscore_must_use)]
505 let _ = sender.send(AsyncMessage::FileOpenDirectoryLoaded(result));
506 }
507 });
508 } else {
509 if let Some(state) = &mut self.file_open_state {
511 state.set_error("Async runtime not available".to_string());
512 }
513 }
514 }
515
516 pub(super) fn handle_file_open_directory_loaded(
518 &mut self,
519 result: std::io::Result<Vec<crate::services::fs::DirEntry>>,
520 ) {
521 match result {
522 Ok(entries) => {
523 if let Some(state) = &mut self.file_open_state {
524 state.set_entries(entries);
525 }
526 let filter = self
528 .prompt
529 .as_ref()
530 .map(|p| p.input.clone())
531 .unwrap_or_default();
532 if !filter.is_empty() {
533 if let Some(state) = &mut self.file_open_state {
534 state.apply_filter(&filter);
535 }
536 }
537 }
538 Err(e) => {
539 if let Some(state) = &mut self.file_open_state {
540 state.set_error(e.to_string());
541 }
542 }
543 }
544 }
545
546 pub(super) fn load_file_open_shortcuts_async(&mut self) {
550 if let Some(ref runtime) = self.tokio_runtime {
551 let filesystem = self.authority.filesystem.clone();
552 let sender = self.async_bridge.as_ref().map(|b| b.sender());
553
554 runtime.spawn(async move {
555 let shortcuts = tokio::task::spawn_blocking(move || {
557 file_open::FileOpenState::build_shortcuts_async(&*filesystem)
558 })
559 .await
560 .unwrap_or_default();
561
562 if let Some(sender) = sender {
563 #[allow(clippy::let_underscore_must_use)]
565 let _ = sender.send(AsyncMessage::FileOpenShortcutsLoaded(shortcuts));
566 }
567 });
568 }
569 }
570
571 pub(super) fn handle_file_open_shortcuts_loaded(
573 &mut self,
574 shortcuts: Vec<file_open::NavigationShortcut>,
575 ) {
576 if let Some(state) = &mut self.file_open_state {
577 state.merge_async_shortcuts(shortcuts);
578 }
579 }
580
581 pub fn cancel_prompt(&mut self) {
583 let theme_to_restore = if let Some(ref prompt) = self.prompt {
585 if let PromptType::SelectTheme { original_theme } = &prompt.prompt_type {
586 Some(original_theme.clone())
587 } else {
588 None
589 }
590 } else {
591 None
592 };
593
594 if let Some(ref prompt) = self.prompt {
596 if let Some(key) = Self::prompt_type_to_history_key(&prompt.prompt_type) {
598 if let Some(history) = self.prompt_histories.get_mut(&key) {
599 history.reset_navigation();
600 }
601 }
602 match &prompt.prompt_type {
603 PromptType::Search | PromptType::ReplaceSearch | PromptType::QueryReplaceSearch => {
604 self.clear_search_highlights();
605 }
606 PromptType::Plugin { custom_type } => {
607 use crate::services::plugins::hooks::HookArgs;
609 self.plugin_manager.run_hook(
610 "prompt_cancelled",
611 HookArgs::PromptCancelled {
612 prompt_type: custom_type.clone(),
613 input: prompt.input.clone(),
614 },
615 );
616 }
617 PromptType::LspRename { overlay_handle, .. } => {
618 let remove_overlay_event = crate::model::event::Event::RemoveOverlay {
620 handle: overlay_handle.clone(),
621 };
622 self.apply_event_to_active_buffer(&remove_overlay_event);
623 }
624 PromptType::OpenFile | PromptType::SwitchProject | PromptType::SaveFileAs => {
625 self.file_open_state = None;
627 self.file_browser_layout = None;
628 }
629 PromptType::AsyncPrompt => {
630 if let Some(callback_id) = self.pending_async_prompt_callback.take() {
632 self.plugin_manager
633 .resolve_callback(callback_id, "null".to_string());
634 }
635 }
636 PromptType::QuickOpen => {
637 if let Some((provider, _)) = self.quick_open_registry.get_provider_for_input("")
639 {
640 if let Some(fp) = provider
641 .as_any()
642 .downcast_ref::<crate::input::quick_open::providers::FileProvider>(
643 ) {
644 fp.cancel_loading();
645 }
646 }
647 self.restore_goto_line_preview_snapshot();
650 }
651 PromptType::GotoLine => {
652 self.restore_goto_line_preview_snapshot();
655 }
656 _ => {}
657 }
658 }
659
660 self.prompt = None;
661 self.pending_search_range = None;
662 self.status_message = Some(t!("search.cancelled").to_string());
663
664 if let Some(original_theme) = theme_to_restore {
666 self.preview_theme(&original_theme);
667 }
668 }
669
670 pub fn handle_prompt_scroll(&mut self, delta: i32) -> bool {
673 if let Some(ref mut prompt) = self.prompt {
674 if prompt.suggestions.is_empty() {
675 return false;
676 }
677
678 let current = prompt.selected_suggestion.unwrap_or(0);
679 let len = prompt.suggestions.len();
680
681 let new_selected = if delta < 0 {
684 current.saturating_sub((-delta) as usize)
686 } else {
687 (current + delta as usize).min(len.saturating_sub(1))
689 };
690
691 prompt.selected_suggestion = Some(new_selected);
692
693 if !matches!(prompt.prompt_type, PromptType::Plugin { .. }) {
695 if let Some(suggestion) = prompt.suggestions.get(new_selected) {
696 prompt.input = suggestion.get_value().to_string();
697 prompt.cursor_pos = prompt.input.len();
698 }
699 }
700
701 return true;
702 }
703 false
704 }
705
706 pub fn confirm_prompt(&mut self) -> Option<(String, PromptType, Option<usize>)> {
711 if let Some(prompt) = self.prompt.take() {
712 let selected_index = prompt.selected_suggestion;
713 let mut final_input = if prompt.sync_input_on_navigate {
715 prompt.input.clone()
718 } else if matches!(
719 prompt.prompt_type,
720 PromptType::OpenFile
721 | PromptType::SwitchProject
722 | PromptType::SaveFileAs
723 | PromptType::StopLspServer
724 | PromptType::RestartLspServer
725 | PromptType::SelectTheme { .. }
726 | PromptType::SelectLocale
727 | PromptType::SwitchToTab
728 | PromptType::SetLanguage
729 | PromptType::SetEncoding
730 | PromptType::SetLineEnding
731 | PromptType::Plugin { .. }
732 ) {
733 if let Some(selected_idx) = prompt.selected_suggestion {
735 if let Some(suggestion) = prompt.suggestions.get(selected_idx) {
736 if suggestion.disabled {
738 self.set_status_message(
739 t!(
740 "error.command_not_available",
741 command = suggestion.text.clone()
742 )
743 .to_string(),
744 );
745 return None;
746 }
747 suggestion.get_value().to_string()
749 } else {
750 prompt.input.clone()
751 }
752 } else {
753 prompt.input.clone()
754 }
755 } else {
756 prompt.input.clone()
757 };
758
759 if matches!(
761 prompt.prompt_type,
762 PromptType::StopLspServer | PromptType::RestartLspServer
763 ) {
764 let is_valid = prompt
765 .suggestions
766 .iter()
767 .any(|s| s.text == final_input || s.get_value() == final_input);
768 if !is_valid {
769 self.prompt = Some(prompt);
771 self.set_status_message(
772 t!("error.no_lsp_match", input = final_input.clone()).to_string(),
773 );
774 return None;
775 }
776 }
777
778 if matches!(prompt.prompt_type, PromptType::RemoveRuler) {
782 if prompt.input.is_empty() {
783 if let Some(selected_idx) = prompt.selected_suggestion {
785 if let Some(suggestion) = prompt.suggestions.get(selected_idx) {
786 final_input = suggestion.get_value().to_string();
787 }
788 } else {
789 self.prompt = Some(prompt);
790 return None;
791 }
792 } else {
793 let typed = prompt.input.trim().to_string();
795 let matched = prompt.suggestions.iter().find(|s| s.get_value() == typed);
796 if let Some(suggestion) = matched {
797 final_input = suggestion.get_value().to_string();
798 } else {
799 self.prompt = Some(prompt);
801 return None;
802 }
803 }
804 }
805
806 if let Some(key) = Self::prompt_type_to_history_key(&prompt.prompt_type) {
808 let history = self.get_or_create_prompt_history(&key);
809 history.push(final_input.clone());
810 history.reset_navigation();
811 }
812
813 Some((final_input, prompt.prompt_type, selected_index))
814 } else {
815 None
816 }
817 }
818
819 pub fn is_prompting(&self) -> bool {
821 self.prompt.is_some()
822 }
823
824 pub(super) fn get_or_create_prompt_history(
826 &mut self,
827 key: &str,
828 ) -> &mut crate::input::input_history::InputHistory {
829 self.prompt_histories.entry(key.to_string()).or_default()
830 }
831
832 pub(super) fn get_prompt_history(
834 &self,
835 key: &str,
836 ) -> Option<&crate::input::input_history::InputHistory> {
837 self.prompt_histories.get(key)
838 }
839
840 pub(super) fn prompt_type_to_history_key(
842 prompt_type: &crate::view::prompt::PromptType,
843 ) -> Option<String> {
844 use crate::view::prompt::PromptType;
845 match prompt_type {
846 PromptType::Search | PromptType::ReplaceSearch | PromptType::QueryReplaceSearch => {
847 Some("search".to_string())
848 }
849 PromptType::Replace { .. } | PromptType::QueryReplace { .. } => {
850 Some("replace".to_string())
851 }
852 PromptType::GotoLine => Some("goto_line".to_string()),
853 PromptType::Plugin { custom_type } => Some(format!("plugin:{}", custom_type)),
854 _ => None,
855 }
856 }
857
858 pub fn editor_mode(&self) -> Option<String> {
861 self.editor_mode.clone()
862 }
863
864 pub fn command_registry(&self) -> &Arc<RwLock<CommandRegistry>> {
866 &self.command_registry
867 }
868
869 pub fn plugin_manager(&self) -> &PluginManager {
871 &self.plugin_manager
872 }
873
874 pub fn plugin_manager_mut(&mut self) -> &mut PluginManager {
876 &mut self.plugin_manager
877 }
878
879 pub fn file_explorer_is_focused(&self) -> bool {
881 self.key_context == KeyContext::FileExplorer
882 }
883
884 pub fn prompt_input(&self) -> Option<&str> {
886 self.prompt.as_ref().map(|p| p.input.as_str())
887 }
888
889 pub fn has_active_selection(&self) -> bool {
891 self.active_cursors().primary().selection_range().is_some()
892 }
893
894 pub fn prompt_mut(&mut self) -> Option<&mut Prompt> {
896 self.prompt.as_mut()
897 }
898
899 pub fn set_status_message(&mut self, message: String) {
901 tracing::info!(target: "status", "{}", message);
902 self.plugin_status_message = None;
903 self.status_message = Some(message);
904 }
905
906 pub fn get_status_message(&self) -> Option<&String> {
908 self.plugin_status_message
909 .as_ref()
910 .or(self.status_message.as_ref())
911 }
912
913 pub fn get_plugin_errors(&self) -> &[String] {
916 &self.plugin_errors
917 }
918
919 pub fn clear_plugin_errors(&mut self) {
921 self.plugin_errors.clear();
922 }
923
924 pub fn update_prompt_suggestions(&mut self) {
926 let (prompt_type, input) = if let Some(prompt) = &self.prompt {
928 (prompt.prompt_type.clone(), prompt.input.clone())
929 } else {
930 return;
931 };
932
933 match prompt_type {
934 PromptType::QuickOpen => {
935 self.update_quick_open_suggestions(&input);
937 }
938 PromptType::Search | PromptType::ReplaceSearch | PromptType::QueryReplaceSearch => {
939 self.update_search_highlights(&input);
941 if let Some(history) = self.prompt_histories.get_mut("search") {
943 history.reset_navigation();
944 }
945 }
946 PromptType::Replace { .. } | PromptType::QueryReplace { .. } => {
947 if let Some(history) = self.prompt_histories.get_mut("replace") {
949 history.reset_navigation();
950 }
951 }
952 PromptType::GotoLine => {
953 if let Some(history) = self.prompt_histories.get_mut("goto_line") {
955 history.reset_navigation();
956 }
957 let input = input.trim();
961 let target = if self.config.editor.relative_line_numbers {
962 None
963 } else {
964 input.parse::<usize>().ok().filter(|&n| n > 0)
965 };
966 self.apply_goto_line_preview(target);
967 }
968 PromptType::OpenFile | PromptType::SwitchProject | PromptType::SaveFileAs => {
969 self.update_file_open_filter();
971 }
972 PromptType::Plugin { custom_type } => {
973 let key = format!("plugin:{}", custom_type);
975 if let Some(history) = self.prompt_histories.get_mut(&key) {
976 history.reset_navigation();
977 }
978 use crate::services::plugins::hooks::HookArgs;
980 self.plugin_manager.run_hook(
981 "prompt_changed",
982 HookArgs::PromptChanged {
983 prompt_type: custom_type,
984 input,
985 },
986 );
987 if let Some(prompt) = &mut self.prompt {
992 prompt.filter_suggestions(false);
993 }
994 }
995 PromptType::SwitchToTab
996 | PromptType::SelectTheme { .. }
997 | PromptType::StopLspServer
998 | PromptType::RestartLspServer
999 | PromptType::SetLanguage
1000 | PromptType::SetEncoding
1001 | PromptType::SetLineEnding => {
1002 if let Some(prompt) = &mut self.prompt {
1003 prompt.filter_suggestions(false);
1004 }
1005 }
1006 PromptType::SelectLocale => {
1007 if let Some(prompt) = &mut self.prompt {
1009 prompt.filter_suggestions(true);
1010 }
1011 }
1012 _ => {}
1013 }
1014 }
1015}