1use std::path::{Path, PathBuf};
2
3use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
4use lsp_types::CompletionItem;
5use markdown_lsp::{DiagnosticsManager, LspEvent, LspManager};
6use miette::Result;
7
8use crate::config::Config;
9use crate::document::{CursorMovement, DocumentBuffer, FileType};
10use crate::navigation::{FileLocation, NavigationHistory};
11use crate::renderer::{CodeRenderer, ImageManager};
12use crate::ui::{FileTree, SearchField, SearchMode};
13
14pub struct App {
16 buffer: DocumentBuffer,
18 should_quit: bool,
20 scroll_offset: usize,
22 status_message: Option<String>,
24 file_tree: Option<FileTree>,
26 show_file_browser: bool,
28 current_dir: PathBuf,
30 config: Config,
32 image_manager: ImageManager,
34 code_renderer: CodeRenderer,
36 lsp_manager: Option<LspManager>,
38 diagnostics_manager: DiagnosticsManager,
40 document_version: i32,
42 navigation_history: NavigationHistory,
44 pending_definition_request: Option<(PathBuf, usize, usize)>,
46 completion_items: Vec<CompletionItem>,
48 filtered_completion_items: Vec<CompletionItem>,
50 completion_selected: usize,
52 show_completion: bool,
54 completion_start_column: usize,
56 quit_confirm_pending: bool,
58 show_quit_dialog: bool,
60 show_line_numbers: bool,
62 show_current_line_highlight: bool,
64 show_search_dialog: bool,
66 search_query: String,
68 replace_query: String,
70 search_mode: SearchMode,
72 search_results: Vec<(usize, usize)>,
74 search_index: Option<usize>,
76 search_active_field: SearchField,
78 show_save_as_dialog: bool,
80 save_as_filename: String,
82 show_goto_line_dialog: bool,
84 goto_line_input: String,
86 show_mq_query_dialog: bool,
88 mq_query_input: String,
90 mq_query_result: Option<String>,
92 pipe_mode: bool,
94}
95
96impl App {
97 pub fn new() -> Self {
99 let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
100 let config = Config::load_or_default();
101 let mut image_manager = ImageManager::new();
102 image_manager.set_base_path(current_dir.clone());
103
104 let lsp_manager = Some(LspManager::new(
106 config.lsp_server_configs(),
107 current_dir.clone(),
108 ));
109
110 let show_line_numbers = config.editor.show_line_numbers;
111 let show_current_line_highlight = config.editor.show_current_line_highlight;
112 let mut code_renderer = CodeRenderer::with_theme(&config.editor.theme);
113 code_renderer.set_use_semantic_tokens(config.editor.use_semantic_tokens);
114
115 Self {
116 buffer: DocumentBuffer::new(),
117 should_quit: false,
118 scroll_offset: 0,
119 status_message: None,
120 file_tree: None,
121 show_file_browser: false,
122 current_dir,
123 config,
124 image_manager,
125 code_renderer,
126 lsp_manager,
127 diagnostics_manager: DiagnosticsManager::new(),
128 document_version: 0,
129 navigation_history: NavigationHistory::new(),
130 pending_definition_request: None,
131 completion_items: Vec::new(),
132 filtered_completion_items: Vec::new(),
133 completion_selected: 0,
134 show_completion: false,
135 completion_start_column: 0,
136 quit_confirm_pending: false,
137 show_quit_dialog: false,
138 show_line_numbers,
139 show_current_line_highlight,
140 show_search_dialog: false,
141 search_query: String::new(),
142 replace_query: String::new(),
143 search_mode: SearchMode::Find,
144 search_results: Vec::new(),
145 search_index: None,
146 search_active_field: SearchField::Search,
147 show_save_as_dialog: false,
148 save_as_filename: String::new(),
149 show_goto_line_dialog: false,
150 goto_line_input: String::new(),
151 show_mq_query_dialog: false,
152 mq_query_input: String::new(),
153 mq_query_result: None,
154 pipe_mode: false,
155 }
156 }
157
158 pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
160 let path = path.as_ref();
161 let buffer = DocumentBuffer::from_file(path)?;
162 let current_dir = path
163 .parent()
164 .map(|p| p.to_path_buf())
165 .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
166 let config = Config::load_or_default();
167 let mut image_manager = ImageManager::new();
168 image_manager.set_base_path(path.to_path_buf());
169
170 let mut lsp_manager = Some(LspManager::new(
172 config.lsp_server_configs(),
173 current_dir.clone(),
174 ));
175
176 let diagnostics_manager = DiagnosticsManager::new();
178 let language_id = file_type_to_language_id(buffer.file_type());
179 if let Some(ref mut lsp) = lsp_manager
180 && let Some(lang_id) = &language_id
181 && lsp.is_enabled(lang_id)
182 {
183 let content = buffer.content();
184 if let Err(e) = lsp.did_open(lang_id, path, &content) {
185 eprintln!("LSP did_open error: {}", e);
186 }
187 }
188
189 let show_line_numbers = config.editor.show_line_numbers;
190 let show_current_line_highlight = config.editor.show_current_line_highlight;
191 let mut code_renderer = CodeRenderer::with_theme(&config.editor.theme);
192 code_renderer.set_use_semantic_tokens(config.editor.use_semantic_tokens);
193
194 Ok(Self {
195 buffer,
196 should_quit: false,
197 scroll_offset: 0,
198 status_message: None,
199 file_tree: None,
200 show_file_browser: false,
201 current_dir,
202 config,
203 image_manager,
204 code_renderer,
205 lsp_manager,
206 diagnostics_manager,
207 document_version: 1,
208 navigation_history: NavigationHistory::new(),
209 pending_definition_request: None,
210 completion_items: Vec::new(),
211 filtered_completion_items: Vec::new(),
212 completion_selected: 0,
213 show_completion: false,
214 completion_start_column: 0,
215 quit_confirm_pending: false,
216 show_quit_dialog: false,
217 show_line_numbers,
218 show_current_line_highlight,
219 show_search_dialog: false,
220 search_query: String::new(),
221 replace_query: String::new(),
222 search_mode: SearchMode::Find,
223 search_results: Vec::new(),
224 search_index: None,
225 search_active_field: SearchField::Search,
226 show_save_as_dialog: false,
227 save_as_filename: String::new(),
228 show_goto_line_dialog: false,
229 goto_line_input: String::new(),
230 show_mq_query_dialog: false,
231 mq_query_input: String::new(),
232 mq_query_result: None,
233 pipe_mode: false,
234 })
235 }
236
237 pub fn from_string(content: &str) -> Result<Self> {
239 let buffer = DocumentBuffer::from_string(content)?;
240 let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
241 let config = Config::load_or_default();
242 let mut image_manager = ImageManager::new();
243 image_manager.set_base_path(current_dir.clone());
244
245 let lsp_manager = Some(LspManager::new(
246 config.lsp_server_configs(),
247 current_dir.clone(),
248 ));
249
250 let show_line_numbers = config.editor.show_line_numbers;
251 let show_current_line_highlight = config.editor.show_current_line_highlight;
252 let mut code_renderer = CodeRenderer::with_theme(&config.editor.theme);
253 code_renderer.set_use_semantic_tokens(config.editor.use_semantic_tokens);
254
255 Ok(Self {
256 buffer,
257 should_quit: false,
258 scroll_offset: 0,
259 status_message: None,
260 file_tree: None,
261 show_file_browser: false,
262 current_dir,
263 config,
264 image_manager,
265 code_renderer,
266 lsp_manager,
267 diagnostics_manager: DiagnosticsManager::new(),
268 document_version: 0,
269 navigation_history: NavigationHistory::new(),
270 pending_definition_request: None,
271 completion_items: Vec::new(),
272 filtered_completion_items: Vec::new(),
273 completion_selected: 0,
274 show_completion: false,
275 completion_start_column: 0,
276 quit_confirm_pending: false,
277 show_quit_dialog: false,
278 show_line_numbers,
279 show_current_line_highlight,
280 show_search_dialog: false,
281 search_query: String::new(),
282 replace_query: String::new(),
283 search_mode: SearchMode::Find,
284 search_results: Vec::new(),
285 search_index: None,
286 search_active_field: SearchField::Search,
287 show_save_as_dialog: false,
288 save_as_filename: String::new(),
289 show_goto_line_dialog: false,
290 goto_line_input: String::new(),
291 show_mq_query_dialog: false,
292 mq_query_input: String::new(),
293 mq_query_result: None,
294 pipe_mode: false,
295 })
296 }
297
298 pub fn buffer(&self) -> &DocumentBuffer {
300 &self.buffer
301 }
302
303 pub fn buffer_mut(&mut self) -> &mut DocumentBuffer {
305 &mut self.buffer
306 }
307
308 pub fn set_pipe_mode(&mut self, pipe_mode: bool) {
310 self.pipe_mode = pipe_mode;
311 }
312
313 pub fn should_quit(&self) -> bool {
315 self.should_quit
316 }
317
318 pub fn scroll_offset(&self) -> usize {
320 self.scroll_offset
321 }
322
323 pub fn image_manager(&self) -> &ImageManager {
325 &self.image_manager
326 }
327
328 pub fn image_manager_mut(&mut self) -> &mut ImageManager {
330 &mut self.image_manager
331 }
332
333 pub fn diagnostics_manager(&self) -> &DiagnosticsManager {
335 &self.diagnostics_manager
336 }
337
338 pub fn code_renderer(&self) -> &CodeRenderer {
340 &self.code_renderer
341 }
342
343 pub fn code_renderer_mut(&mut self) -> &mut CodeRenderer {
345 &mut self.code_renderer
346 }
347
348 pub fn status_message(&self) -> Option<&str> {
350 self.status_message.as_deref()
351 }
352
353 pub fn set_status_message(&mut self, message: String) {
355 self.status_message = Some(message);
356 }
357
358 pub fn clear_status_message(&mut self) {
360 self.status_message = None;
361 }
362
363 pub fn filtered_completion_items(&self) -> &[CompletionItem] {
365 &self.filtered_completion_items
366 }
367
368 pub fn completion_selected(&self) -> usize {
370 self.completion_selected
371 }
372
373 pub fn show_completion(&self) -> bool {
375 self.show_completion
376 }
377
378 fn get_completion_prefix(&self) -> String {
380 let cursor = self.buffer.cursor();
381 let line = self.buffer.line(cursor.line).unwrap_or_default();
382 if cursor.column > self.completion_start_column {
383 line.chars()
384 .skip(self.completion_start_column)
385 .take(cursor.column - self.completion_start_column)
386 .collect()
387 } else {
388 String::new()
389 }
390 }
391
392 fn filter_completion_items(&mut self) {
394 let prefix = self.get_completion_prefix().to_lowercase();
395 self.filtered_completion_items = self
396 .completion_items
397 .iter()
398 .filter(|item| {
399 if prefix.is_empty() {
400 true
401 } else {
402 item.label.to_lowercase().contains(&prefix)
403 }
404 })
405 .cloned()
406 .collect();
407
408 if self.filtered_completion_items.is_empty()
410 || self.completion_selected >= self.filtered_completion_items.len()
411 {
412 self.completion_selected = 0;
413 }
414
415 if self.filtered_completion_items.is_empty() && !prefix.is_empty() {
417 self.show_completion = false;
418 }
419 }
420
421 pub fn file_tree(&self) -> Option<&FileTree> {
423 self.file_tree.as_ref()
424 }
425
426 pub fn file_tree_mut(&mut self) -> Option<&mut FileTree> {
428 self.file_tree.as_mut()
429 }
430
431 pub fn is_file_browser_visible(&self) -> bool {
433 self.show_file_browser
434 }
435
436 pub fn toggle_file_browser(&mut self) {
438 self.show_file_browser = !self.show_file_browser;
439
440 if self.show_file_browser && self.file_tree.is_none() {
442 self.file_tree = Some(FileTree::new(&self.current_dir));
443 }
444 }
445
446 pub fn show_line_numbers(&self) -> bool {
448 self.show_line_numbers
449 }
450
451 pub fn toggle_line_numbers(&mut self) {
453 self.show_line_numbers = !self.show_line_numbers;
454 }
455
456 pub fn show_current_line_highlight(&self) -> bool {
458 self.show_current_line_highlight
459 }
460
461 pub fn toggle_current_line_highlight(&mut self) {
463 self.show_current_line_highlight = !self.show_current_line_highlight;
464 }
465
466 pub fn line_number_gutter_width(&self) -> u16 {
468 if self.show_line_numbers {
469 let total_lines = self.buffer.line_count();
470 let digits = if total_lines == 0 {
471 1
472 } else {
473 ((total_lines as f64).log10().floor() as usize) + 1
474 }
475 .max(3);
476 (digits + 3) as u16
478 } else {
479 0
480 }
481 }
482
483 pub fn open_file(&mut self, path: impl AsRef<Path>) -> Result<()> {
485 let path = path.as_ref();
486 self.buffer = DocumentBuffer::from_file(path)?;
487
488 if let Some(parent) = path.parent() {
489 self.current_dir = parent.to_path_buf();
490 }
491
492 self.scroll_offset = 0;
493 self.document_version = 1;
494
495 let language_id = file_type_to_language_id(self.buffer.file_type());
497 if let Some(ref mut lsp) = self.lsp_manager
498 && let Some(lang_id) = &language_id
499 && lsp.is_enabled(lang_id)
500 {
501 let content = self.buffer.content();
502 if let Err(e) = lsp.did_open(lang_id, path, &content) {
503 eprintln!("LSP did_open error: {}", e);
504 }
505 }
506
507 Ok(())
508 }
509
510 pub fn poll_lsp_events(&mut self) {
512 let events = if let Some(ref mut lsp) = self.lsp_manager {
514 lsp.poll_events()
515 } else {
516 Vec::new()
517 };
518
519 for (_language_id, event) in events {
521 match event {
522 LspEvent::Diagnostics(params) => {
523 self.diagnostics_manager.update(params.diagnostics);
525 }
526 LspEvent::SemanticTokens(_uri, tokens) => {
527 let content = self.buffer.content();
529 let decoded_tokens =
530 crate::renderer::code::decode_semantic_tokens(&tokens, &content);
531 self.code_renderer.set_semantic_tokens(decoded_tokens);
532 }
533 LspEvent::Completion(completion) => {
534 use lsp_types::CompletionResponse;
536
537 let items = match completion {
538 CompletionResponse::Array(items) => items,
539 CompletionResponse::List(list) => list.items,
540 };
541
542 if !items.is_empty() {
543 self.completion_items = items;
544 self.completion_selected = 0;
546 self.show_completion = true;
547 self.filter_completion_items();
548 }
549 }
550 LspEvent::Definition(response) => {
551 if let Some((file_path, line, column)) = self.pending_definition_request.take()
554 {
555 self.navigation_history
556 .push(FileLocation::new(file_path, line, column));
557 }
558
559 if let Err(e) = self.jump_to_definition(response) {
561 self.set_status_message(format!("Failed to jump to definition: {}", e));
562 }
563 }
564 LspEvent::References(_locations) => {
565 }
568 LspEvent::Initialized(trigger_chars) => {
569 if let Some(ref mut lsp) = self.lsp_manager {
571 if let FileType::Code(lang_id) = self.buffer.file_type() {
573 lsp.set_trigger_characters(lang_id, trigger_chars);
574 } else if let FileType::Markdown = self.buffer.file_type() {
575 lsp.set_trigger_characters("markdown", trigger_chars);
576 }
577
578 let language_id = file_type_to_language_id(self.buffer.file_type());
579 if let Some(file_path) = self.buffer.file_path()
580 && let Some(lang_id) = &language_id
581 && lsp.is_enabled(lang_id)
582 {
583 let path_buf = file_path.to_path_buf();
584 if let Err(e) = lsp.request_semantic_tokens(lang_id, &path_buf) {
585 eprintln!("Failed to request semantic tokens: {}", e);
586 }
587 }
588 }
589 }
590 LspEvent::Error(err) => {
591 eprintln!("LSP error: {}", err);
592 }
593 }
594 }
595 }
596
597 fn notify_lsp_document_change(&mut self) {
599 let language_id = file_type_to_language_id(self.buffer.file_type());
600 if let Some(ref mut lsp) = self.lsp_manager
601 && let Some(file_path) = self.buffer.file_path()
602 && let Some(lang_id) = &language_id
603 && lsp.is_enabled(lang_id)
604 {
605 self.document_version += 1;
606 let content = self.buffer.content();
607 let path_buf = file_path.to_path_buf();
608 if let Err(e) = lsp.did_change(lang_id, &path_buf, self.document_version, &content) {
609 eprintln!("LSP did_change error: {}", e);
610 }
611
612 if let Err(e) = lsp.request_semantic_tokens(lang_id, &path_buf) {
614 eprintln!("Failed to request semantic tokens: {}", e);
615 }
616 }
617 }
618
619 pub fn request_go_to_definition(&mut self) -> Result<()> {
621 let language_id = file_type_to_language_id(self.buffer.file_type());
622 if let Some(ref mut lsp) = self.lsp_manager {
623 if let Some(file_path) = self.buffer.file_path() {
624 if let Some(lang_id) = &language_id
625 && lsp.is_enabled(lang_id)
626 {
627 let cursor = self.buffer.cursor();
628 let path_buf = file_path.to_path_buf();
629
630 self.pending_definition_request =
632 Some((path_buf.clone(), cursor.line, cursor.column));
633
634 lsp.request_definition(
635 lang_id,
636 &path_buf,
637 cursor.line as u32,
638 cursor.column as u32,
639 )
640 .map_err(|e| miette::miette!("Failed to request definition: {}", e))?;
641
642 self.set_status_message("Requesting definition...".to_string());
643 } else {
644 self.set_status_message("LSP not available for this file type".to_string());
645 }
646 } else {
647 self.set_status_message("No file open".to_string());
648 }
649 } else {
650 self.set_status_message("LSP not initialized".to_string());
651 }
652 Ok(())
653 }
654
655 fn jump_to_definition(&mut self, response: lsp_types::GotoDefinitionResponse) -> Result<()> {
657 use lsp_types::GotoDefinitionResponse;
658
659 let location = match response {
661 GotoDefinitionResponse::Scalar(loc) => Some(loc),
662 GotoDefinitionResponse::Array(locs) => locs.into_iter().next(),
663 GotoDefinitionResponse::Link(links) => {
664 links.into_iter().next().map(|link| lsp_types::Location {
666 uri: link.target_uri,
667 range: link.target_selection_range,
668 })
669 }
670 };
671
672 if let Some(loc) = location {
673 let path_str = loc.uri.path().as_str();
675 let path = PathBuf::from(path_str);
676
677 self.jump_to_location(
679 &path,
680 loc.range.start.line as usize,
681 loc.range.start.character as usize,
682 )?;
683 self.set_status_message(format!(
684 "Jumped to {}:{}:{}",
685 path.display(),
686 loc.range.start.line + 1,
687 loc.range.start.character + 1
688 ));
689 } else {
690 self.set_status_message("No definition found".to_string());
691 }
692
693 Ok(())
694 }
695
696 fn jump_to_location(&mut self, file_path: &Path, line: usize, column: usize) -> Result<()> {
698 if self
700 .buffer
701 .file_path()
702 .map(|p| p != file_path)
703 .unwrap_or(true)
704 {
705 self.open_file(file_path)?;
706 }
707
708 let cursor = self.buffer.cursor_mut();
710 cursor.line = line;
711 cursor.column = column;
712 self.adjust_scroll();
713
714 Ok(())
715 }
716
717 pub fn navigate_back(&mut self) -> Result<()> {
719 if let Some(prev) = self.navigation_history.back() {
721 let prev = prev.clone(); self.jump_to_location(&prev.path, prev.line, prev.column)?;
723 self.set_status_message(format!(
724 "Back to {}:{}:{}",
725 prev.path.display(),
726 prev.line + 1,
727 prev.column + 1
728 ));
729 } else {
730 self.set_status_message("No previous location in history".to_string());
731 }
732
733 Ok(())
734 }
735
736 pub fn navigate_forward(&mut self) -> Result<()> {
738 if let Some(next) = self.navigation_history.forward() {
740 let next = next.clone(); self.jump_to_location(&next.path, next.line, next.column)?;
742 self.set_status_message(format!(
743 "Forward to {}:{}:{}",
744 next.path.display(),
745 next.line + 1,
746 next.column + 1
747 ));
748 } else {
749 self.set_status_message("No next location in history".to_string());
750 }
751
752 Ok(())
753 }
754
755 pub fn request_completion(&mut self, trigger_character: Option<String>) -> Result<()> {
757 let language_id = file_type_to_language_id(self.buffer.file_type());
758 if let Some(ref mut lsp) = self.lsp_manager {
759 if let Some(file_path) = self.buffer.file_path() {
760 if let Some(lang_id) = &language_id
761 && lsp.is_enabled(lang_id)
762 {
763 let cursor = self.buffer.cursor();
764 let path_buf = file_path.to_path_buf();
765
766 self.completion_start_column =
769 self.buffer.word_start_column(cursor.line, cursor.column);
770
771 lsp.request_completion(
772 lang_id,
773 &path_buf,
774 cursor.line as u32,
775 cursor.column as u32,
776 trigger_character,
777 )
778 .map_err(|e| miette::miette!("Failed to request completion: {}", e))?;
779 } else {
780 self.set_status_message("LSP not available for this file type".to_string());
781 }
782 } else {
783 self.set_status_message("No file open".to_string());
784 }
785 } else {
786 self.set_status_message("LSP not initialized".to_string());
787 }
788 Ok(())
789 }
790
791 pub fn apply_completion(&mut self) -> Result<()> {
793 if self.show_completion && !self.filtered_completion_items.is_empty() {
794 let item = self.filtered_completion_items[self.completion_selected].clone();
795
796 let insert_text = item.insert_text.as_ref().unwrap_or(&item.label).clone();
798
799 self.buffer.delete_range(self.completion_start_column);
801
802 self.buffer.insert_str(&insert_text);
804
805 self.show_completion = false;
807 self.completion_items.clear();
808 self.filtered_completion_items.clear();
809
810 self.notify_lsp_document_change();
812 }
813 Ok(())
814 }
815
816 pub fn completion_up(&mut self) {
818 if self.show_completion && !self.filtered_completion_items.is_empty() {
819 if self.completion_selected > 0 {
820 self.completion_selected -= 1;
821 } else {
822 self.completion_selected = self.filtered_completion_items.len() - 1;
823 }
824 }
825 }
826
827 pub fn completion_down(&mut self) {
829 if self.show_completion && !self.filtered_completion_items.is_empty() {
830 if self.completion_selected < self.filtered_completion_items.len() - 1 {
831 self.completion_selected += 1;
832 } else {
833 self.completion_selected = 0;
834 }
835 }
836 }
837
838 pub fn cancel_completion(&mut self) {
840 self.show_completion = false;
841 self.completion_items.clear();
842 self.filtered_completion_items.clear();
843 self.completion_selected = 0;
844 }
845
846 pub fn show_quit_dialog(&self) -> bool {
848 self.show_quit_dialog
849 }
850
851 pub fn confirm_quit(&mut self) {
853 self.should_quit = true;
854 self.show_quit_dialog = false;
855 }
856
857 pub fn cancel_quit_dialog(&mut self) {
859 self.show_quit_dialog = false;
860 self.quit_confirm_pending = false;
861 }
862
863 pub fn show_search_dialog(&self) -> bool {
865 self.show_search_dialog
866 }
867
868 pub fn search_query(&self) -> &str {
870 &self.search_query
871 }
872
873 pub fn replace_query(&self) -> &str {
875 &self.replace_query
876 }
877
878 pub fn search_mode(&self) -> SearchMode {
880 self.search_mode
881 }
882
883 pub fn search_match_count(&self) -> usize {
885 self.search_results.len()
886 }
887
888 pub fn search_current_index(&self) -> Option<usize> {
890 self.search_index
891 }
892
893 pub fn search_active_field(&self) -> SearchField {
895 self.search_active_field
896 }
897
898 pub fn show_save_as_dialog(&self) -> bool {
900 self.show_save_as_dialog
901 }
902
903 pub fn save_as_filename(&self) -> &str {
905 &self.save_as_filename
906 }
907
908 pub fn show_goto_line_dialog(&self) -> bool {
910 self.show_goto_line_dialog
911 }
912
913 pub fn goto_line_input(&self) -> &str {
915 &self.goto_line_input
916 }
917
918 pub fn open_goto_line_dialog(&mut self) {
920 self.show_goto_line_dialog = true;
921 self.goto_line_input.clear();
922 }
923
924 pub fn close_goto_line_dialog(&mut self) {
926 self.show_goto_line_dialog = false;
927 self.goto_line_input.clear();
928 }
929
930 pub fn confirm_goto_line(&mut self) -> Result<()> {
932 if self.goto_line_input.is_empty() {
933 return Err(miette::miette!("Line number cannot be empty"));
934 }
935
936 let line_number: usize = self
937 .goto_line_input
938 .trim()
939 .parse()
940 .map_err(|_| miette::miette!("Invalid line number"))?;
941
942 if line_number == 0 {
943 return Err(miette::miette!("Line number must be greater than 0"));
944 }
945
946 let target_line = line_number.saturating_sub(1);
948 let max_line = self.buffer.line_count().saturating_sub(1);
949
950 let target_line = target_line.min(max_line);
952
953 self.buffer.cursor_mut().line = target_line;
955 self.buffer.cursor_mut().column = 0;
956 self.adjust_scroll();
957
958 self.set_status_message(format!("Jumped to line {}", line_number));
959 self.close_goto_line_dialog();
960
961 Ok(())
962 }
963
964 pub fn show_mq_query_dialog(&self) -> bool {
966 self.show_mq_query_dialog
967 }
968
969 pub fn mq_query_input(&self) -> &str {
971 &self.mq_query_input
972 }
973
974 pub fn mq_query_result(&self) -> Option<&str> {
976 self.mq_query_result.as_deref()
977 }
978
979 pub fn open_mq_query_dialog(&mut self) {
981 self.show_mq_query_dialog = true;
982 self.mq_query_input.clear();
983 self.mq_query_result = None;
984 }
985
986 pub fn close_mq_query_dialog(&mut self) {
988 self.show_mq_query_dialog = false;
989 self.mq_query_input.clear();
990 self.mq_query_result = None;
991 }
992
993 pub fn execute_mq_query(&mut self) {
995 if self.mq_query_input.is_empty() {
996 self.mq_query_result = Some("Error: Query is empty".to_string());
997 return;
998 }
999
1000 let content = self.buffer.content();
1001 let query = self.mq_query_input.clone();
1002
1003 let mut engine = mq_lang::DefaultEngine::default();
1004 engine.load_builtin_module();
1005
1006 let input = match mq_lang::parse_markdown_input(&content) {
1007 Ok(input) => input,
1008 Err(e) => {
1009 self.mq_query_result = Some(format!("Error: {}", e));
1010 return;
1011 }
1012 };
1013
1014 match engine.eval(&query, input.into_iter()) {
1015 Ok(results) => {
1016 let output = results
1017 .into_iter()
1018 .filter_map(|v| {
1019 if v.is_empty() {
1020 None
1021 } else {
1022 Some(v.to_string())
1023 }
1024 })
1025 .collect::<Vec<_>>()
1026 .join("\n");
1027 let start_line = self.buffer.cursor().line;
1028 let start_column = self.buffer.cursor().column;
1029 self.buffer.insert_str(&output);
1030 self.buffer.cursor_mut().line = start_line;
1031 self.buffer.cursor_mut().column = start_column;
1032 self.buffer.cursor_mut().update_desired_column();
1033 self.adjust_scroll();
1034 self.notify_lsp_document_change();
1035 self.set_status_message("mq query executed successfully.".to_string());
1036 self.close_mq_query_dialog();
1037 }
1038 Err(e) => {
1039 self.mq_query_result = Some(format!("Error: {}", e));
1040 }
1041 }
1042 }
1043
1044 pub fn open_save_as_dialog(&mut self) {
1046 self.show_save_as_dialog = true;
1047 if self.buffer.file_path().is_none() {
1049 self.save_as_filename = "untitled".to_string();
1050 } else if let Some(path) = self.buffer.file_path() {
1051 self.save_as_filename = path
1052 .file_name()
1053 .and_then(|n| n.to_str())
1054 .unwrap_or("untitled")
1055 .to_string();
1056 }
1057 }
1058
1059 pub fn close_save_as_dialog(&mut self) {
1061 self.show_save_as_dialog = false;
1062 self.save_as_filename.clear();
1063 }
1064
1065 pub fn confirm_save_as(&mut self) -> Result<()> {
1067 if self.save_as_filename.is_empty() {
1068 return Err(miette::miette!("Filename cannot be empty"));
1069 }
1070
1071 let path = if self.save_as_filename.contains('/') || self.save_as_filename.contains('\\') {
1073 PathBuf::from(&self.save_as_filename)
1075 } else {
1076 self.current_dir.join(&self.save_as_filename)
1078 };
1079
1080 self.buffer.save_as(&path)?;
1082 self.set_status_message(format!("Saved as: {}", path.display()));
1083 self.close_save_as_dialog();
1084
1085 let file_type = FileType::from_path(&path);
1087 let language_id = file_type_to_language_id(&file_type);
1088 if let Some(lsp) = &mut self.lsp_manager
1089 && let Some(lang_id) = &language_id
1090 && lsp.is_enabled(lang_id)
1091 {
1092 let content = self.buffer.content();
1094 if let Err(e) = lsp.did_open(lang_id, &path, &content) {
1095 eprintln!("LSP did_open error: {}", e);
1096 }
1097 }
1098
1099 Ok(())
1100 }
1101
1102 pub fn open_search(&mut self) {
1104 self.show_search_dialog = true;
1105 self.search_mode = SearchMode::Find;
1106 self.search_active_field = SearchField::Search;
1107 }
1108
1109 pub fn open_replace(&mut self) {
1111 self.show_search_dialog = true;
1112 self.search_mode = SearchMode::Replace;
1113 self.search_active_field = SearchField::Search;
1114 }
1115
1116 pub fn close_search(&mut self) {
1118 self.show_search_dialog = false;
1119 }
1120
1121 fn update_search_results(&mut self) {
1123 self.search_results = self.buffer.find_all(&self.search_query);
1124 if self.search_results.is_empty() {
1125 self.search_index = None;
1126 } else if self.search_index.is_none()
1127 || self.search_index.unwrap() >= self.search_results.len()
1128 {
1129 self.search_index = Some(0);
1130 }
1131 }
1132
1133 pub fn search_next(&mut self) {
1135 if self.search_results.is_empty() {
1136 return;
1137 }
1138 if let Some(idx) = self.search_index {
1139 self.search_index = Some((idx + 1) % self.search_results.len());
1140 } else {
1141 self.search_index = Some(0);
1142 }
1143 self.jump_to_current_match();
1144 }
1145
1146 pub fn search_prev(&mut self) {
1148 if self.search_results.is_empty() {
1149 return;
1150 }
1151 if let Some(idx) = self.search_index {
1152 if idx == 0 {
1153 self.search_index = Some(self.search_results.len() - 1);
1154 } else {
1155 self.search_index = Some(idx - 1);
1156 }
1157 } else {
1158 self.search_index = Some(self.search_results.len() - 1);
1159 }
1160 self.jump_to_current_match();
1161 }
1162
1163 fn jump_to_current_match(&mut self) {
1165 if let Some(idx) = self.search_index
1166 && let Some(&(line, column)) = self.search_results.get(idx)
1167 {
1168 self.buffer.cursor_mut().line = line;
1169 self.buffer.cursor_mut().column = column;
1170 self.adjust_scroll();
1171 }
1172 }
1173
1174 pub fn replace_current(&mut self) {
1176 if let Some(idx) = self.search_index
1177 && let Some(&(line, column)) = self.search_results.get(idx)
1178 && self
1179 .buffer
1180 .replace_at(line, column, &self.search_query, &self.replace_query)
1181 {
1182 self.notify_lsp_document_change();
1183 self.update_search_results();
1184 if !self.search_results.is_empty() {
1186 if idx >= self.search_results.len() {
1187 self.search_index = Some(0);
1188 }
1189 self.jump_to_current_match();
1190 }
1191 }
1192 }
1193
1194 pub fn replace_all(&mut self) {
1196 let count = self
1197 .buffer
1198 .replace_all(&self.search_query, &self.replace_query);
1199 if count > 0 {
1200 self.notify_lsp_document_change();
1201 self.set_status_message(format!("Replaced {} occurrences", count));
1202 self.update_search_results();
1203 }
1204 }
1205
1206 pub fn toggle_search_field(&mut self) {
1208 if self.search_mode == SearchMode::Replace {
1209 self.search_active_field = match self.search_active_field {
1210 SearchField::Search => SearchField::Replace,
1211 SearchField::Replace => SearchField::Search,
1212 };
1213 }
1214 }
1215
1216 pub fn handle_key(&mut self, key: KeyEvent) -> Result<()> {
1218 self.clear_status_message();
1220
1221 if self.show_quit_dialog {
1223 match key.code {
1224 KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
1225 self.confirm_quit();
1226 return Ok(());
1227 }
1228 KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
1229 self.cancel_quit_dialog();
1230 return Ok(());
1231 }
1232 _ => {
1233 return Ok(());
1235 }
1236 }
1237 }
1238
1239 if self.show_search_dialog {
1241 return self.handle_search_key(key);
1242 }
1243
1244 if self.show_save_as_dialog {
1246 return self.handle_save_as_key(key);
1247 }
1248
1249 if self.show_goto_line_dialog {
1251 return self.handle_goto_line_key(key);
1252 }
1253
1254 if self.show_mq_query_dialog {
1256 return self.handle_mq_query_key(key);
1257 }
1258
1259 if self.show_completion {
1261 match key.code {
1262 KeyCode::Up => {
1263 self.completion_up();
1264 return Ok(());
1265 }
1266 KeyCode::Down => {
1267 self.completion_down();
1268 return Ok(());
1269 }
1270 KeyCode::Enter => {
1271 return self.apply_completion();
1272 }
1273 KeyCode::Esc => {
1274 self.cancel_completion();
1275 return Ok(());
1276 }
1277 KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
1278 self.buffer.insert_char(c);
1280 self.notify_lsp_document_change();
1281 self.filter_completion_items();
1282 return Ok(());
1283 }
1284 KeyCode::Backspace => {
1285 let cursor = self.buffer.cursor();
1287 if cursor.column <= self.completion_start_column {
1288 self.cancel_completion();
1290 }
1291 self.buffer.delete_char();
1292 self.notify_lsp_document_change();
1293 if self.show_completion {
1294 self.filter_completion_items();
1295 }
1296 return Ok(());
1297 }
1298 _ => {
1299 self.cancel_completion();
1301 }
1303 }
1304 }
1305
1306 if self.config.keybindings.toggle_file_browser.matches(&key)
1308 || self
1309 .config
1310 .keybindings
1311 .toggle_file_browser_alt
1312 .matches(&key)
1313 {
1314 self.toggle_file_browser();
1315 return Ok(());
1316 }
1317
1318 if self.show_file_browser {
1320 return self.handle_file_browser_key(key);
1321 }
1322
1323 if self.config.keybindings.quit.matches(&key)
1325 || self.config.keybindings.quit_alt.matches(&key)
1326 {
1327 if self.buffer.is_modified() && !self.pipe_mode {
1328 self.show_quit_dialog = true;
1330 self.set_status_message("Unsaved changes! Save before quitting?".to_string());
1331 } else {
1332 self.should_quit = true;
1333 }
1334 return Ok(());
1335 }
1336
1337 self.quit_confirm_pending = false;
1339
1340 if self.config.keybindings.save.matches(&key) {
1341 if self.buffer.file_path().is_none() {
1343 self.open_save_as_dialog();
1345 } else {
1346 if let Err(e) = self.buffer.save() {
1348 self.set_status_message(format!("Error saving file: {}", e));
1349 } else {
1350 self.set_status_message("File saved successfully.".to_string());
1351 }
1352 }
1353 return Ok(());
1354 }
1355
1356 if self.config.keybindings.goto_definition.matches(&key) {
1358 return self.request_go_to_definition();
1359 }
1360
1361 if self.config.keybindings.navigate_back.matches(&key) {
1363 return self.navigate_back();
1364 }
1365 if self.config.keybindings.navigate_forward.matches(&key) {
1366 return self.navigate_forward();
1367 }
1368
1369 if self.config.keybindings.toggle_line_numbers.matches(&key) {
1371 self.toggle_line_numbers();
1372 return Ok(());
1373 }
1374
1375 if self
1377 .config
1378 .keybindings
1379 .toggle_current_line_highlight
1380 .matches(&key)
1381 {
1382 self.toggle_current_line_highlight();
1383 return Ok(());
1384 }
1385
1386 if self.config.keybindings.search.matches(&key) {
1388 self.open_search();
1389 return Ok(());
1390 }
1391
1392 if self.config.keybindings.replace.matches(&key) {
1394 self.open_replace();
1395 return Ok(());
1396 }
1397
1398 if self.config.keybindings.goto_line.matches(&key) {
1400 self.open_goto_line_dialog();
1401 return Ok(());
1402 }
1403
1404 if self.config.keybindings.execute_mq_query.matches(&key) {
1406 self.open_mq_query_dialog();
1407 return Ok(());
1408 }
1409
1410 if self.config.keybindings.undo.matches(&key) {
1412 self.buffer.undo();
1413 self.notify_lsp_document_change();
1414 self.adjust_scroll();
1415 return Ok(());
1416 }
1417
1418 if self.config.keybindings.redo.matches(&key) {
1420 self.buffer.redo();
1421 self.notify_lsp_document_change();
1422 self.adjust_scroll();
1423 return Ok(());
1424 }
1425
1426 if key.code == KeyCode::Char(' ') && key.modifiers.contains(KeyModifiers::CONTROL) {
1428 return self.request_completion(None);
1429 }
1430
1431 match key.code {
1433 KeyCode::Up => {
1435 self.buffer.move_cursor(CursorMovement::Up);
1436 self.adjust_scroll();
1437 }
1438 KeyCode::Down => {
1439 self.buffer.move_cursor(CursorMovement::Down);
1440 self.adjust_scroll();
1441 }
1442 KeyCode::Left => {
1443 self.buffer.move_cursor(CursorMovement::Left);
1444 }
1445 KeyCode::Right => {
1446 self.buffer.move_cursor(CursorMovement::Right);
1447 }
1448 KeyCode::Home => {
1449 self.buffer.move_cursor(CursorMovement::StartOfLine);
1450 }
1451 KeyCode::End => {
1452 self.buffer.move_cursor(CursorMovement::EndOfLine);
1453 }
1454 KeyCode::PageUp => {
1455 self.buffer.move_cursor(CursorMovement::PageUp);
1456 self.adjust_scroll();
1457 }
1458 KeyCode::PageDown => {
1459 self.buffer.move_cursor(CursorMovement::PageDown);
1460 self.adjust_scroll();
1461 }
1462
1463 KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
1465 self.buffer.insert_char(c);
1466 self.notify_lsp_document_change();
1467 let language_id = file_type_to_language_id(self.buffer.file_type());
1469 if let Some(ref lsp) = self.lsp_manager
1470 && let Some(lang_id) = &language_id
1471 && lsp.is_trigger_character(lang_id, c)
1472 {
1473 let _ = self.request_completion(Some(c.to_string()));
1475 }
1476 }
1477 KeyCode::Enter => {
1478 self.buffer.insert_newline();
1479 self.adjust_scroll();
1480 self.notify_lsp_document_change();
1481 }
1482 KeyCode::Backspace => {
1483 self.buffer.delete_char();
1484 self.notify_lsp_document_change();
1485 }
1486 KeyCode::Tab => {
1487 self.buffer.insert_char('\t');
1488 self.notify_lsp_document_change();
1489 }
1490
1491 _ => {}
1492 }
1493
1494 Ok(())
1495 }
1496
1497 pub fn handle_paste(&mut self, text: String) -> Result<()> {
1499 if self.show_file_browser {
1501 return Ok(());
1502 }
1503
1504 self.buffer.insert_str(&text);
1506 self.adjust_scroll();
1507 self.notify_lsp_document_change();
1508
1509 Ok(())
1510 }
1511
1512 fn adjust_scroll(&mut self) {
1514 let cursor_line = self.buffer.cursor().line;
1515 let viewport_height = 20; if cursor_line >= self.scroll_offset + viewport_height {
1519 self.scroll_offset = cursor_line - viewport_height + 1;
1520 }
1521
1522 if cursor_line < self.scroll_offset {
1524 self.scroll_offset = cursor_line;
1525 }
1526 }
1527
1528 fn handle_file_browser_key(&mut self, key: KeyEvent) -> Result<()> {
1530 match key.code {
1531 KeyCode::Up => {
1533 if let Some(tree) = self.file_tree_mut() {
1534 tree.move_up();
1535 }
1536 }
1537 KeyCode::Down => {
1538 if let Some(tree) = self.file_tree_mut() {
1539 tree.move_down();
1540 }
1541 }
1542 KeyCode::Enter => {
1543 if let Some(tree) = self.file_tree_mut()
1545 && let Some(item) = tree.selected_item()
1546 {
1547 if item.is_dir {
1548 tree.toggle_expand();
1549 } else {
1550 let path = item.path.clone();
1552 let _ = tree; if let Err(e) = self.open_file(&path) {
1554 self.set_status_message(format!("Error opening file: {}", e));
1555 } else {
1556 self.show_file_browser = false;
1557 self.set_status_message(format!("Opened: {}", path.display()));
1558 }
1559 }
1560 }
1561 }
1562 KeyCode::Left => {
1563 if let Some(tree) = self.file_tree_mut()
1565 && let Some(item) = tree.selected_item()
1566 && item.is_dir
1567 && item.expanded
1568 {
1569 tree.toggle_expand();
1570 }
1571 }
1572 KeyCode::Right => {
1573 if let Some(tree) = self.file_tree_mut()
1575 && let Some(item) = tree.selected_item()
1576 && item.is_dir
1577 && !item.expanded
1578 {
1579 tree.toggle_expand();
1580 }
1581 }
1582 _ if self.config.keybindings.close_browser.matches(&key) => {
1583 self.show_file_browser = false;
1585 }
1586 _ => {}
1587 }
1588
1589 Ok(())
1590 }
1591
1592 fn handle_search_key(&mut self, key: KeyEvent) -> Result<()> {
1594 match key.code {
1595 KeyCode::Esc => {
1596 self.close_search();
1597 }
1598 KeyCode::Enter => {
1599 if key.modifiers.contains(KeyModifiers::SHIFT) {
1600 self.search_prev();
1601 } else {
1602 self.search_next();
1603 }
1604 }
1605 KeyCode::Tab => {
1606 self.toggle_search_field();
1607 }
1608 KeyCode::Backspace => match self.search_active_field {
1609 SearchField::Search => {
1610 self.search_query.pop();
1611 self.update_search_results();
1612 }
1613 SearchField::Replace => {
1614 self.replace_query.pop();
1615 }
1616 },
1617 KeyCode::Char('r') if key.modifiers.contains(KeyModifiers::CONTROL) => {
1618 if self.search_mode == SearchMode::Replace {
1620 self.replace_current();
1621 }
1622 }
1623 KeyCode::Char('a') if key.modifiers.contains(KeyModifiers::CONTROL) => {
1624 if self.search_mode == SearchMode::Replace {
1626 self.replace_all();
1627 }
1628 }
1629 KeyCode::Char(c) => {
1630 match self.search_active_field {
1631 SearchField::Search => {
1632 self.search_query.push(c);
1633 self.update_search_results();
1634 if !self.search_results.is_empty() {
1636 self.search_index = Some(0);
1637 self.jump_to_current_match();
1638 }
1639 }
1640 SearchField::Replace => {
1641 self.replace_query.push(c);
1642 }
1643 }
1644 }
1645 _ => {}
1646 }
1647 Ok(())
1648 }
1649
1650 fn handle_save_as_key(&mut self, key: KeyEvent) -> Result<()> {
1652 match key.code {
1653 KeyCode::Esc => {
1654 self.close_save_as_dialog();
1655 }
1656 KeyCode::Enter => {
1657 if let Err(e) = self.confirm_save_as() {
1658 self.set_status_message(format!("Error saving file: {}", e));
1659 self.close_save_as_dialog();
1660 }
1661 }
1662 KeyCode::Backspace => {
1663 self.save_as_filename.pop();
1664 }
1665 KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
1666 self.save_as_filename.push(c);
1667 }
1668 _ => {}
1669 }
1670 Ok(())
1671 }
1672
1673 fn handle_mq_query_key(&mut self, key: KeyEvent) -> Result<()> {
1675 match key.code {
1676 KeyCode::Esc => {
1677 self.close_mq_query_dialog();
1678 }
1679 KeyCode::Enter => {
1680 self.execute_mq_query();
1681 }
1682 KeyCode::Backspace => {
1683 self.mq_query_input.pop();
1684 self.mq_query_result = None;
1685 }
1686 KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
1687 self.mq_query_input.push(c);
1688 self.mq_query_result = None;
1689 }
1690 _ => {}
1691 }
1692 Ok(())
1693 }
1694
1695 fn handle_goto_line_key(&mut self, key: KeyEvent) -> Result<()> {
1697 match key.code {
1698 KeyCode::Esc => {
1699 self.close_goto_line_dialog();
1700 }
1701 KeyCode::Enter => {
1702 if let Err(e) = self.confirm_goto_line() {
1703 self.set_status_message(format!("{}", e));
1704 }
1705 }
1706 KeyCode::Backspace => {
1707 self.goto_line_input.pop();
1708 }
1709 KeyCode::Char(c) if c.is_ascii_digit() => {
1710 self.goto_line_input.push(c);
1711 }
1712 _ => {}
1713 }
1714 Ok(())
1715 }
1716}
1717
1718impl Default for App {
1719 fn default() -> Self {
1720 Self::new()
1721 }
1722}
1723
1724fn file_type_to_language_id(file_type: &FileType) -> Option<String> {
1726 match file_type {
1727 FileType::Code(lang_id) => Some(lang_id.clone()),
1728 FileType::Markdown => Some("markdown".to_string()),
1729 FileType::PlainText => None,
1730 }
1731}