use crate::app::TextEditor; use eframe::egui; use super::find_highlight::draw_find_highlight; pub(super) fn editor_view( ui: &mut egui::Ui, app: &mut TextEditor, ) -> (egui::Response, Option) { let current_match_position = app.get_current_match_position(); let show_find = app.show_find; let prev_show_find = app.prev_show_find; let show_preferences = app.show_preferences; let show_about = app.show_about; let show_shortcuts = app.show_shortcuts; let word_wrap = app.word_wrap; let font_size = app.font_size; // Check if reset zoom was requested in previous frame let reset_zoom_key = egui::Id::new("editor_reset_zoom"); let should_reset_zoom = ui.ctx().memory_mut(|mem| { mem.data.get_temp::(reset_zoom_key).unwrap_or(false) }); // Reset zoom if requested if should_reset_zoom { app.zoom_factor = 1.0; ui.ctx().set_zoom_factor(1.0); ui.ctx().memory_mut(|mem| { mem.data.insert_temp(reset_zoom_key, false); }); } if let Some(active_tab) = app.get_active_tab_mut() { let bg_color = ui.visuals().extreme_bg_color; let editor_rect = ui.available_rect_before_wrap(); ui.painter().rect_filled(editor_rect, 0.0, bg_color); let desired_width = if word_wrap { ui.available_width() } else { f32::INFINITY }; let text_edit = egui::TextEdit::multiline(&mut active_tab.content) .frame(false) .font(egui::TextStyle::Monospace) .code_editor() .desired_width(desired_width) .desired_rows(0) .lock_focus(true) .cursor_at_end(false) .id(egui::Id::new("main_text_editor")); let output = text_edit.show(ui); // Store text length for context menu let text_len = active_tab.content.len(); // Right-click context menu output.response.context_menu(|ui| { if ui.button("Cut").clicked() { ui.ctx().input_mut(|i| i.events.push(egui::Event::Cut)); ui.close_menu(); } if ui.button("Copy").clicked() { ui.ctx().input_mut(|i| i.events.push(egui::Event::Copy)); ui.close_menu(); } if ui.button("Paste").clicked() { ui.ctx() .send_viewport_cmd(egui::ViewportCommand::RequestPaste); ui.close_menu(); } if ui.button("Delete").clicked() { ui.ctx().input_mut(|i| { i.events.push(egui::Event::Key { key: egui::Key::Delete, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE, }) }); ui.close_menu(); } if ui.button("Select All").clicked() { let text_edit_id = egui::Id::new("main_text_editor"); if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) { let select_all_range = egui::text::CCursorRange::two( egui::text::CCursor::new(0), egui::text::CCursor::new(text_len), ); state.cursor.set_char_range(Some(select_all_range)); egui::TextEdit::store_state(ui.ctx(), text_edit_id, state); } ui.close_menu(); } ui.separator(); if ui.button("Reset Zoom").clicked() { ui.ctx().memory_mut(|mem| { mem.data.insert_temp(reset_zoom_key, true); }); ui.close_menu(); } }); let cursor_rect = if let Some(cursor_range) = output.state.cursor.char_range() { let cursor_pos = cursor_range.primary.index; let content = &active_tab.content; // Count newlines up to cursor position using char_indices to avoid char boundary issues let cursor_line = content .char_indices() .take_while(|(byte_pos, _)| *byte_pos < cursor_pos) .filter(|(_, ch)| *ch == '\n') .count(); let font_id = ui .style() .text_styles .get(&egui::TextStyle::Monospace) .unwrap_or(&egui::FontId::monospace(font_size)) .clone(); let line_height = ui.fonts(|fonts| fonts.row_height(&font_id)); let y_pos = output.response.rect.top() + (cursor_line as f32 * line_height); let cursor_rect = egui::Rect::from_min_size( egui::pos2(output.response.rect.left(), y_pos), egui::vec2(2.0, line_height), ); Some(cursor_rect) } else { None }; if !show_find && prev_show_find { if let Some((start_pos, end_pos)) = current_match_position { let text_edit_id = egui::Id::new("main_text_editor"); if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) { let cursor_range = egui::text::CCursorRange::two( egui::text::CCursor::new(start_pos), egui::text::CCursor::new(end_pos), ); state.cursor.set_char_range(Some(cursor_range)); egui::TextEdit::store_state(ui.ctx(), text_edit_id, state); } } } if show_find { if let Some((start_pos, end_pos)) = current_match_position { draw_find_highlight( ui, &active_tab.content, start_pos, end_pos, output.response.rect, font_size, ); } } if output.response.changed() { active_tab.update_modified_state(); app.find_matches.clear(); app.current_match_index = None; } if !output.response.has_focus() && !show_preferences && !show_about && !show_shortcuts && !show_find { output.response.request_focus(); } (output.response, cursor_rect) } else { (ui.label("No file open, how did you get here?"), None) } } pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) { let word_wrap = app.word_wrap; if word_wrap { let (_response, _cursor_rect) = editor_view(ui, app); } else { let estimated_width = app.calculate_content_based_width(ui); let output = egui::ScrollArea::horizontal() .auto_shrink([false; 2]) .show(ui, |ui| { ui.allocate_ui_with_layout( egui::Vec2::new(estimated_width, ui.available_height()), egui::Layout::left_to_right(egui::Align::TOP), |ui| editor_view(ui, app), ) }); let editor_response = &output.inner.inner.0; if let Some(cursor_rect) = output.inner.inner.1 { let text_edit_id = egui::Id::new("main_text_editor"); let current_cursor_pos = if let Some(state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) { state.cursor.char_range().map(|range| range.primary.index) } else { None }; let cursor_moved = current_cursor_pos != app.previous_cursor_position; let text_changed = editor_response.changed(); let should_scroll = (cursor_moved || text_changed) && { let visible_area = ui.clip_rect(); !visible_area.intersects(cursor_rect) }; if should_scroll { ui.scroll_to_rect(cursor_rect, Some(egui::Align::Center)); } app.previous_cursor_position = current_cursor_pos; } } }