Compare commits

..

No commits in common. "83cff76b5ae92899f700c15bb8760cc8d02792c4" and "46bca1c6cbe4bb22869d5e80445f292b1fbba536" have entirely different histories.

15 changed files with 151 additions and 252 deletions

View File

@ -143,7 +143,7 @@ fn get_shortcuts() -> Vec<ShortcutDefinition> {
] ]
} }
fn execute_action(action: ShortcutAction, editor: &mut TextEditor, _ctx: &egui::Context) -> bool { fn execute_action(action: ShortcutAction, editor: &mut TextEditor, ctx: &egui::Context) -> bool {
match action { match action {
ShortcutAction::NewFile => { ShortcutAction::NewFile => {
io::new_file(editor); io::new_file(editor);
@ -171,9 +171,7 @@ fn execute_action(action: ShortcutAction, editor: &mut TextEditor, _ctx: &egui::
if let Some(current_tab) = editor.get_active_tab() { if let Some(current_tab) = editor.get_active_tab() {
if current_tab.is_modified { if current_tab.is_modified {
// Show dialog for unsaved changes // Show dialog for unsaved changes
editor.pending_unsaved_action = Some( editor.pending_unsaved_action = Some(super::state::UnsavedAction::CloseTab(editor.active_tab_index));
super::state::UnsavedAction::CloseTab(editor.active_tab_index),
);
} else { } else {
// Close tab directly if no unsaved changes // Close tab directly if no unsaved changes
editor.close_tab(editor.active_tab_index); editor.close_tab(editor.active_tab_index);

View File

@ -1,12 +1,12 @@
use super::editor::TextEditor;
use crate::app::shortcuts;
use crate::ui::about_window::about_window;
use crate::ui::central_panel::central_panel; use crate::ui::central_panel::central_panel;
use crate::ui::find_window::find_window;
use crate::ui::menu_bar::menu_bar; use crate::ui::menu_bar::menu_bar;
use crate::ui::preferences_window::preferences_window;
use crate::ui::shortcuts_window::shortcuts_window;
use crate::ui::tab_bar::tab_bar; use crate::ui::tab_bar::tab_bar;
use crate::ui::about_window::about_window;
use crate::ui::shortcuts_window::shortcuts_window;
use crate::ui::preferences_window::preferences_window;
use crate::ui::find_window::find_window;
use crate::app::shortcuts;
use super::editor::TextEditor;
impl eframe::App for TextEditor { impl eframe::App for TextEditor {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {

View File

@ -30,7 +30,7 @@ impl TextEditor {
tab_bar_rect: None, tab_bar_rect: None,
menu_bar_stable_until: None, menu_bar_stable_until: None,
text_processing_result: std::sync::Arc::new(std::sync::Mutex::new(Default::default())), text_processing_result: std::sync::Arc::new(std::sync::Mutex::new(Default::default())),
_processing_thread_handle: None, processing_thread_handle: None,
find_query: String::new(), find_query: String::new(),
find_matches: Vec::new(), find_matches: Vec::new(),
current_match_index: None, current_match_index: None,

View File

@ -19,7 +19,7 @@ impl Default for TextEditor {
show_line_numbers: false, show_line_numbers: false,
word_wrap: true, word_wrap: true,
auto_hide_toolbar: false, auto_hide_toolbar: false,
auto_hide_tab_bar: true, auto_hide_tab_bar: false,
theme: Theme::default(), theme: Theme::default(),
line_side: false, line_side: false,
font_family: "Proportional".to_string(), font_family: "Proportional".to_string(),
@ -30,17 +30,17 @@ impl Default for TextEditor {
tab_bar_rect: None, tab_bar_rect: None,
menu_bar_stable_until: None, menu_bar_stable_until: None,
text_processing_result: Arc::new(Mutex::new(TextProcessingResult::default())), text_processing_result: Arc::new(Mutex::new(TextProcessingResult::default())),
_processing_thread_handle: None, processing_thread_handle: None,
// Find functionality // Find functionality
find_query: String::new(), find_query: String::new(),
find_matches: Vec::new(), find_matches: Vec::new(),
current_match_index: None, current_match_index: None,
case_sensitive_search: false, case_sensitive_search: false,
prev_show_find: false, prev_show_find: false,
// Cursor tracking for smart scrolling // Cursor tracking for smart scrolling
previous_cursor_position: None, previous_cursor_position: None,
// Track previous content for incremental processing // Track previous content for incremental processing
previous_content: String::new(), previous_content: String::new(),
previous_cursor_char_index: None, previous_cursor_char_index: None,

View File

@ -13,8 +13,8 @@ pub enum UnsavedAction {
#[derive(Clone)] #[derive(Clone)]
pub struct TextProcessingResult { pub struct TextProcessingResult {
pub line_count: usize, pub line_count: usize,
pub longest_line_index: usize, // Which line is the longest (0-based) pub longest_line_index: usize, // Which line is the longest (0-based)
pub longest_line_length: usize, // Character count of the longest line pub longest_line_length: usize, // Character count of the longest line
pub longest_line_pixel_width: f32, // Actual pixel width of the longest line pub longest_line_pixel_width: f32, // Actual pixel width of the longest line
} }
@ -55,20 +55,20 @@ pub struct TextEditor {
pub(crate) tab_bar_rect: Option<egui::Rect>, pub(crate) tab_bar_rect: Option<egui::Rect>,
pub(crate) menu_bar_stable_until: Option<std::time::Instant>, pub(crate) menu_bar_stable_until: Option<std::time::Instant>,
pub(crate) text_processing_result: Arc<Mutex<TextProcessingResult>>, pub(crate) text_processing_result: Arc<Mutex<TextProcessingResult>>,
pub(crate) _processing_thread_handle: Option<thread::JoinHandle<()>>, pub(crate) processing_thread_handle: Option<thread::JoinHandle<()>>,
pub(crate) find_query: String, pub(crate) find_query: String,
pub(crate) find_matches: Vec<(usize, usize)>, // (start_pos, end_pos) byte positions pub(crate) find_matches: Vec<(usize, usize)>, // (start_pos, end_pos) byte positions
pub(crate) current_match_index: Option<usize>, pub(crate) current_match_index: Option<usize>,
pub(crate) case_sensitive_search: bool, pub(crate) case_sensitive_search: bool,
pub(crate) prev_show_find: bool, // Track previous state to detect transitions pub(crate) prev_show_find: bool, // Track previous state to detect transitions
// Cursor tracking for smart scrolling // Cursor tracking for smart scrolling
pub(crate) previous_cursor_position: Option<usize>, pub(crate) previous_cursor_position: Option<usize>,
// Track previous content for incremental processing // Track previous content for incremental processing
pub(crate) previous_content: String, pub(crate) previous_content: String,
pub(crate) previous_cursor_char_index: Option<usize>, pub(crate) previous_cursor_char_index: Option<usize>,
pub(crate) current_cursor_line: usize, // Track current line number incrementally pub(crate) current_cursor_line: usize, // Track current line number incrementally
pub(crate) previous_cursor_line: usize, // Track previous line for comparison pub(crate) previous_cursor_line: usize, // Track previous line for comparison
pub(crate) font_settings_changed: bool, // Flag to trigger text reprocessing when font changes pub(crate) font_settings_changed: bool, // Flag to trigger text reprocessing when font changes
} }

View File

@ -6,7 +6,7 @@ impl TextEditor {
pub fn process_text_for_rendering(&mut self, content: &str, ui: &egui::Ui) { pub fn process_text_for_rendering(&mut self, content: &str, ui: &egui::Ui) {
let lines: Vec<&str> = content.lines().collect(); let lines: Vec<&str> = content.lines().collect();
let line_count = lines.len().max(1); let line_count = lines.len().max(1);
if lines.is_empty() { if lines.is_empty() {
self.update_processing_result(TextProcessingResult { self.update_processing_result(TextProcessingResult {
line_count: 1, line_count: 1,
@ -17,9 +17,10 @@ impl TextEditor {
return; return;
} }
// Find the longest line by character count first (fast)
let mut longest_line_index = 0; let mut longest_line_index = 0;
let mut longest_line_length = 0; let mut longest_line_length = 0;
for (index, line) in lines.iter().enumerate() { for (index, line) in lines.iter().enumerate() {
let char_count = line.chars().count(); let char_count = line.chars().count();
if char_count > longest_line_length { if char_count > longest_line_length {
@ -28,19 +29,17 @@ impl TextEditor {
} }
} }
// Calculate pixel width for the longest line
let font_id = self.get_font_id(); let font_id = self.get_font_id();
let longest_line_pixel_width = if longest_line_length > 0 { let longest_line_pixel_width = if longest_line_length > 0 {
let longest_line_text = lines[longest_line_index]; let longest_line_text = lines[longest_line_index];
ui.fonts(|fonts| { ui.fonts(|fonts| {
fonts fonts.layout(
.layout( longest_line_text.to_string(),
longest_line_text.to_string(), font_id,
font_id, egui::Color32::WHITE,
egui::Color32::WHITE, f32::INFINITY,
f32::INFINITY, ).size().x
)
.size()
.x
}) })
} else { } else {
0.0 0.0
@ -57,106 +56,65 @@ impl TextEditor {
} }
/// Efficiently detect and process line changes without full content iteration /// Efficiently detect and process line changes without full content iteration
pub fn process_incremental_change( pub fn process_incremental_change(&mut self, old_content: &str, new_content: &str,
&mut self, old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui) {
old_content: &str, // Calculate cursor line change incrementally
new_content: &str, let line_change = self.calculate_cursor_line_change(old_content, new_content, old_cursor_pos, new_cursor_pos);
old_cursor_pos: usize,
new_cursor_pos: usize, // Update current cursor line
ui: &egui::Ui,
) {
let line_change = self.calculate_cursor_line_change(
old_content,
new_content,
old_cursor_pos,
new_cursor_pos,
);
self.current_cursor_line = (self.current_cursor_line as isize + line_change) as usize; self.current_cursor_line = (self.current_cursor_line as isize + line_change) as usize;
// Detect the type of change and handle appropriately
if old_content.len() == new_content.len() { if old_content.len() == new_content.len() {
self.handle_character_replacement( // Same length - likely a character replacement
old_content, self.handle_character_replacement(old_content, new_content, old_cursor_pos, new_cursor_pos, ui);
new_content,
old_cursor_pos,
new_cursor_pos,
ui,
);
} else if new_content.len() > old_content.len() { } else if new_content.len() > old_content.len() {
self.handle_content_addition( // Content added
old_content, self.handle_content_addition(old_content, new_content, old_cursor_pos, new_cursor_pos, ui);
new_content,
old_cursor_pos,
new_cursor_pos,
ui,
);
} else { } else {
self.handle_content_removal( // Content removed
old_content, self.handle_content_removal(old_content, new_content, old_cursor_pos, new_cursor_pos, ui);
new_content,
old_cursor_pos,
new_cursor_pos,
ui,
);
} }
self.previous_cursor_line = self.current_cursor_line; self.previous_cursor_line = self.current_cursor_line;
} }
/// Calculate the change in cursor line without full iteration /// Calculate the change in cursor line without full iteration
fn calculate_cursor_line_change( fn calculate_cursor_line_change(&self, old_content: &str, new_content: &str,
&self, old_cursor_pos: usize, new_cursor_pos: usize) -> isize {
old_content: &str, // Count newlines up to the cursor position in both contents
new_content: &str,
old_cursor_pos: usize,
new_cursor_pos: usize,
) -> isize {
let old_newlines = old_content[..old_cursor_pos.min(old_content.len())] let old_newlines = old_content[..old_cursor_pos.min(old_content.len())]
.bytes() .bytes()
.filter(|&b| b == b'\n') .filter(|&b| b == b'\n')
.count(); .count();
let new_newlines = new_content[..new_cursor_pos.min(new_content.len())] let new_newlines = new_content[..new_cursor_pos.min(new_content.len())]
.bytes() .bytes()
.filter(|&b| b == b'\n') .filter(|&b| b == b'\n')
.count(); .count();
new_newlines as isize - old_newlines as isize new_newlines as isize - old_newlines as isize
} }
/// Handle character replacement (same length change) /// Handle character replacement (same length change)
fn handle_character_replacement( fn handle_character_replacement(&mut self, old_content: &str, new_content: &str,
&mut self, old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui) {
_old_content: &str, // Extract the current line from new content
new_content: &str,
_old_cursor_pos: usize,
new_cursor_pos: usize,
ui: &egui::Ui,
) {
let current_line = self.extract_current_line(new_content, new_cursor_pos); let current_line = self.extract_current_line(new_content, new_cursor_pos);
let current_line_length = current_line.chars().count(); let current_line_length = current_line.chars().count();
self.update_line_if_longer( self.update_line_if_longer(self.current_cursor_line, &current_line, current_line_length, ui);
self.current_cursor_line,
&current_line,
current_line_length,
ui,
);
} }
/// Handle content addition /// Handle content addition
fn handle_content_addition( fn handle_content_addition(&mut self, old_content: &str, new_content: &str,
&mut self, old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui) {
old_content: &str, // Find the common prefix and suffix to identify the added text
new_content: &str,
_old_cursor_pos: usize,
new_cursor_pos: usize,
ui: &egui::Ui,
) {
let min_len = old_content.len().min(new_content.len()); let min_len = old_content.len().min(new_content.len());
let mut common_prefix = 0; let mut common_prefix = 0;
let mut common_suffix = 0; let mut common_suffix = 0;
// Find common prefix
for i in 0..min_len { for i in 0..min_len {
if old_content.as_bytes()[i] == new_content.as_bytes()[i] { if old_content.as_bytes()[i] == new_content.as_bytes()[i] {
common_prefix += 1; common_prefix += 1;
@ -164,7 +122,8 @@ impl TextEditor {
break; break;
} }
} }
// Find common suffix
for i in 0..min_len - common_prefix { for i in 0..min_len - common_prefix {
let old_idx = old_content.len() - 1 - i; let old_idx = old_content.len() - 1 - i;
let new_idx = new_content.len() - 1 - i; let new_idx = new_content.len() - 1 - i;
@ -174,42 +133,36 @@ impl TextEditor {
break; break;
} }
} }
// Extract the added text
let added_start = common_prefix; let added_start = common_prefix;
let added_end = new_content.len() - common_suffix; let added_end = new_content.len() - common_suffix;
let added_text = &new_content[added_start..added_end]; let added_text = &new_content[added_start..added_end];
let newlines_added = added_text.bytes().filter(|&b| b == b'\n').count(); let newlines_added = added_text.bytes().filter(|&b| b == b'\n').count();
if newlines_added > 0 { if newlines_added > 0 {
// Lines were added, update line count
let mut current_result = self.get_text_processing_result(); let mut current_result = self.get_text_processing_result();
current_result.line_count += newlines_added; current_result.line_count += newlines_added;
self.update_processing_result(current_result); self.update_processing_result(current_result);
} }
// Check if the current line is now longer
let current_line = self.extract_current_line(new_content, new_cursor_pos); let current_line = self.extract_current_line(new_content, new_cursor_pos);
let current_line_length = current_line.chars().count(); let current_line_length = current_line.chars().count();
self.update_line_if_longer( self.update_line_if_longer(self.current_cursor_line, &current_line, current_line_length, ui);
self.current_cursor_line,
&current_line,
current_line_length,
ui,
);
} }
/// Handle content removal /// Handle content removal
fn handle_content_removal( fn handle_content_removal(&mut self, old_content: &str, new_content: &str,
&mut self, old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui) {
old_content: &str, // Find the common prefix and suffix to identify the removed text
new_content: &str,
_old_cursor_pos: usize,
new_cursor_pos: usize,
ui: &egui::Ui,
) {
let min_len = old_content.len().min(new_content.len()); let min_len = old_content.len().min(new_content.len());
let mut common_prefix = 0; let mut common_prefix = 0;
let mut common_suffix = 0; let mut common_suffix = 0;
// Find common prefix
for i in 0..min_len { for i in 0..min_len {
if old_content.as_bytes()[i] == new_content.as_bytes()[i] { if old_content.as_bytes()[i] == new_content.as_bytes()[i] {
common_prefix += 1; common_prefix += 1;
@ -217,7 +170,8 @@ impl TextEditor {
break; break;
} }
} }
// Find common suffix
for i in 0..min_len - common_prefix { for i in 0..min_len - common_prefix {
let old_idx = old_content.len() - 1 - i; let old_idx = old_content.len() - 1 - i;
let new_idx = new_content.len() - 1 - i; let new_idx = new_content.len() - 1 - i;
@ -227,80 +181,77 @@ impl TextEditor {
break; break;
} }
} }
// Extract the removed text
let removed_start = common_prefix; let removed_start = common_prefix;
let removed_end = old_content.len() - common_suffix; let removed_end = old_content.len() - common_suffix;
let removed_text = &old_content[removed_start..removed_end]; let removed_text = &old_content[removed_start..removed_end];
let newlines_removed = removed_text.bytes().filter(|&b| b == b'\n').count(); let newlines_removed = removed_text.bytes().filter(|&b| b == b'\n').count();
if newlines_removed > 0 { if newlines_removed > 0 {
// Lines were removed, update line count
let mut current_result = self.get_text_processing_result(); let mut current_result = self.get_text_processing_result();
current_result.line_count = current_result.line_count.saturating_sub(newlines_removed); current_result.line_count = current_result.line_count.saturating_sub(newlines_removed);
// If we removed the longest line, we need to rescan (but only if necessary)
if self.current_cursor_line <= current_result.longest_line_index { if self.current_cursor_line <= current_result.longest_line_index {
self.process_text_for_rendering(new_content, ui); // The longest line might have been affected, but let's be conservative
// and only rescan if we're sure it was the longest line
if self.current_cursor_line == current_result.longest_line_index {
self.process_text_for_rendering(new_content, ui);
return;
}
} }
self.update_processing_result(current_result); self.update_processing_result(current_result);
} }
// Check if the current line changed
let current_line = self.extract_current_line(new_content, new_cursor_pos); let current_line = self.extract_current_line(new_content, new_cursor_pos);
let current_line_length = current_line.chars().count(); let current_line_length = current_line.chars().count();
// If this was the longest line and it got shorter, we might need to rescan
let current_result = self.get_text_processing_result(); let current_result = self.get_text_processing_result();
if self.current_cursor_line == current_result.longest_line_index if self.current_cursor_line == current_result.longest_line_index &&
&& current_line_length < current_result.longest_line_length current_line_length < current_result.longest_line_length {
{
self.process_text_for_rendering(new_content, ui); self.process_text_for_rendering(new_content, ui);
} else { } else {
self.update_line_if_longer( self.update_line_if_longer(self.current_cursor_line, &current_line, current_line_length, ui);
self.current_cursor_line,
&current_line,
current_line_length,
ui,
);
} }
} }
/// Extract the current line efficiently without full content scan /// Extract the current line efficiently without full content scan
fn extract_current_line(&self, content: &str, cursor_pos: usize) -> String { fn extract_current_line(&self, content: &str, cursor_pos: usize) -> String {
let bytes = content.as_bytes(); let bytes = content.as_bytes();
// Find line start (search backwards from cursor)
let mut line_start = cursor_pos; let mut line_start = cursor_pos;
while line_start > 0 && bytes[line_start - 1] != b'\n' { while line_start > 0 && bytes[line_start - 1] != b'\n' {
line_start -= 1; line_start -= 1;
} }
// Find line end (search forwards from cursor)
let mut line_end = cursor_pos; let mut line_end = cursor_pos;
while line_end < bytes.len() && bytes[line_end] != b'\n' { while line_end < bytes.len() && bytes[line_end] != b'\n' {
line_end += 1; line_end += 1;
} }
content[line_start..line_end].to_string() content[line_start..line_end].to_string()
} }
/// Update longest line info if the current line is longer /// Update longest line info if the current line is longer
fn update_line_if_longer( fn update_line_if_longer(&mut self, line_index: usize, line_content: &str, line_length: usize, ui: &egui::Ui) {
&mut self,
line_index: usize,
line_content: &str,
line_length: usize,
ui: &egui::Ui,
) {
let current_result = self.get_text_processing_result(); let current_result = self.get_text_processing_result();
if line_length > current_result.longest_line_length { if line_length > current_result.longest_line_length {
let font_id = self.get_font_id(); let font_id = self.get_font_id();
let pixel_width = ui.fonts(|fonts| { let pixel_width = ui.fonts(|fonts| {
fonts fonts.layout(
.layout( line_content.to_string(),
line_content.to_string(), font_id,
font_id, egui::Color32::WHITE,
egui::Color32::WHITE, f32::INFINITY,
f32::INFINITY, ).size().x
)
.size()
.x
}); });
let result = TextProcessingResult { let result = TextProcessingResult {
@ -309,7 +260,7 @@ impl TextEditor {
longest_line_length: line_length, longest_line_length: line_length,
longest_line_pixel_width: pixel_width, longest_line_pixel_width: pixel_width,
}; };
self.update_processing_result(result); self.update_processing_result(result);
} }
} }

View File

@ -77,7 +77,7 @@ fn get_pywal_colors() -> Option<egui::Visuals> {
let fg = parse_color(colors.get(7).unwrap_or(&colors[0]))?; let fg = parse_color(colors.get(7).unwrap_or(&colors[0]))?;
let bg_alt = parse_color(colors.get(8).unwrap_or(&colors[0]))?; let bg_alt = parse_color(colors.get(8).unwrap_or(&colors[0]))?;
let accent = parse_color(colors.get(1).unwrap_or(&colors[0]))?; let accent = parse_color(colors.get(1).unwrap_or(&colors[0]))?;
let _secondary = parse_color(colors.get(2).unwrap_or(&colors[0]))?; let secondary = parse_color(colors.get(2).unwrap_or(&colors[0]))?;
let mut visuals = if is_dark_color(bg) { let mut visuals = if is_dark_color(bg) {
egui::Visuals::dark() egui::Visuals::dark()

View File

@ -1,5 +1,5 @@
use crate::app::tab::Tab;
use crate::app::TextEditor; use crate::app::TextEditor;
use crate::app::tab::Tab;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;

View File

@ -5,7 +5,7 @@ use eframe::egui;
mod app; mod app;
mod io; mod io;
mod ui; mod ui;
use app::{config::Config, TextEditor}; use app::{TextEditor, config::Config};
fn main() -> eframe::Result { fn main() -> eframe::Result {
let options = eframe::NativeOptions { let options = eframe::NativeOptions {

View File

@ -14,7 +14,7 @@ pub(crate) fn central_panel(app: &mut TextEditor, ctx: &egui::Context) {
let line_side = app.line_side; let line_side = app.line_side;
let font_size = app.font_size; let font_size = app.font_size;
let _output = egui::CentralPanel::default() let output = egui::CentralPanel::default()
.frame(egui::Frame::NONE) .frame(egui::Frame::NONE)
.show(ctx, |ui| { .show(ctx, |ui| {
let bg_color = ui.visuals().extreme_bg_color; let bg_color = ui.visuals().extreme_bg_color;
@ -23,20 +23,11 @@ pub(crate) fn central_panel(app: &mut TextEditor, ctx: &egui::Context) {
let editor_height = panel_rect.height(); let editor_height = panel_rect.height();
if !show_line_numbers || app.get_active_tab().is_none() { if !show_line_numbers || app.get_active_tab().is_none() {
let _scroll_response = egui::ScrollArea::vertical()
egui::ScrollArea::vertical() .auto_shrink([false; 2])
.auto_shrink([false; 2]) .show(ui, |ui| {
.show(ui, |ui| { editor_view_ui(ui, app);
let full_rect = ui.available_rect_before_wrap(); });
let context_response =
ui.allocate_response(full_rect.size(), egui::Sense::click());
ui.scope_builder(egui::UiBuilder::new().max_rect(full_rect), |ui| {
editor_view_ui(ui, app);
});
show_context_menu(ui, app, &context_response);
});
return; return;
} }
@ -86,8 +77,7 @@ pub(crate) fn central_panel(app: &mut TextEditor, ctx: &egui::Context) {
.show(ui, |ui| { .show(ui, |ui| {
if line_side { if line_side {
// Line numbers on the right // Line numbers on the right
let text_editor_width = let text_editor_width = editor_dimensions.text_width + editor_dimensions.total_reserved_width;
editor_dimensions.text_width + editor_dimensions.total_reserved_width;
ui.allocate_ui_with_layout( ui.allocate_ui_with_layout(
egui::vec2(text_editor_width, editor_height), egui::vec2(text_editor_width, editor_height),
egui::Layout::left_to_right(egui::Align::TOP), egui::Layout::left_to_right(egui::Align::TOP),
@ -97,22 +87,7 @@ pub(crate) fn central_panel(app: &mut TextEditor, ctx: &egui::Context) {
egui::vec2(editor_dimensions.text_width, editor_height), egui::vec2(editor_dimensions.text_width, editor_height),
egui::Layout::left_to_right(egui::Align::TOP), egui::Layout::left_to_right(egui::Align::TOP),
|ui| { |ui| {
// Create an invisible interaction area for context menu editor_view_ui(ui, app);
let full_rect = ui.available_rect_before_wrap();
let context_response = ui.allocate_response(
full_rect.size(),
egui::Sense::click(),
);
// Reset cursor to render editor at the top
ui.scope_builder(
egui::UiBuilder::new().max_rect(full_rect),
|ui| {
editor_view_ui(ui, app);
},
);
show_context_menu(ui, app, &context_response);
}, },
); );
separator_widget(ui); separator_widget(ui);
@ -121,49 +96,30 @@ pub(crate) fn central_panel(app: &mut TextEditor, ctx: &egui::Context) {
); );
} else { } else {
// Line numbers on the left // Line numbers on the left
let text_editor_width = let text_editor_width = editor_dimensions.text_width + editor_dimensions.total_reserved_width;
editor_dimensions.text_width + editor_dimensions.total_reserved_width;
ui.allocate_ui_with_layout( ui.allocate_ui_with_layout(
egui::vec2(text_editor_width, editor_height), egui::vec2(text_editor_width, editor_height),
egui::Layout::left_to_right(egui::Align::TOP), egui::Layout::left_to_right(egui::Align::TOP),
|ui| { |ui| {
line_numbers_widget(ui); line_numbers_widget(ui);
separator_widget(ui); separator_widget(ui);
editor_view_ui(ui, app);
// Create an invisible interaction area for context menu
let editor_area = ui.available_rect_before_wrap();
let context_response =
ui.allocate_response(editor_area.size(), egui::Sense::click());
// Reset cursor to render editor at the current position
ui.scope_builder(
egui::UiBuilder::new().max_rect(editor_area),
|ui| {
editor_view_ui(ui, app);
},
);
show_context_menu(ui, app, &context_response);
}, },
); );
} }
}); });
}); });
}
fn show_context_menu(_ui: &mut egui::Ui, app: &mut TextEditor, context_response: &egui::Response) { output.response.context_menu(|ui| {
context_response.context_menu(|ui| {
let text_len = app.get_active_tab().unwrap().content.len(); let text_len = app.get_active_tab().unwrap().content.len();
let reset_zoom_key = egui::Id::new("editor_reset_zoom"); let reset_zoom_key = egui::Id::new("editor_reset_zoom");
if ui.button("Cut").clicked() { if ui.button("Cut").clicked() {
ui.ctx() ui.ctx().send_viewport_cmd(egui::ViewportCommand::RequestCut);
.send_viewport_cmd(egui::ViewportCommand::RequestCut);
ui.close_menu(); ui.close_menu();
} }
if ui.button("Copy").clicked() { if ui.button("Copy").clicked() {
ui.ctx() ui.ctx().send_viewport_cmd(egui::ViewportCommand::RequestCopy);
.send_viewport_cmd(egui::ViewportCommand::RequestCopy);
ui.close_menu(); ui.close_menu();
} }
if ui.button("Paste").clicked() { if ui.button("Paste").clicked() {
@ -203,4 +159,5 @@ fn show_context_menu(_ui: &mut egui::Ui, app: &mut TextEditor, context_response:
ui.close_menu(); ui.close_menu();
} }
}); });
} }

View File

@ -84,6 +84,7 @@ pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) -> egui::R
.char_range() .char_range()
.map(|range| range.primary.index); .map(|range| range.primary.index);
if let Some(content) = content_for_processing { if let Some(content) = content_for_processing {
let previous_content = app.previous_content.clone(); let previous_content = app.previous_content.clone();
let previous_cursor_pos = app.previous_cursor_char_index; let previous_cursor_pos = app.previous_cursor_char_index;

View File

@ -1,6 +1,6 @@
use eframe::egui; use eframe::egui;
pub(super) fn _draw_find_highlight( pub(super) fn draw_find_highlight(
ui: &mut egui::Ui, ui: &mut egui::Ui,
content: &str, content: &str,
start_pos: usize, start_pos: usize,
@ -74,7 +74,10 @@ pub(super) fn _draw_find_highlight(
egui::vec2(match_width, line_height), egui::vec2(match_width, line_height),
); );
ui.painter() ui.painter().rect_filled(
.rect_filled(highlight_rect, 0.0, ui.visuals().selection.bg_fill); highlight_rect,
0.0,
ui.visuals().selection.bg_fill,
);
} }
} }

View File

@ -86,7 +86,8 @@ pub(super) fn render_line_numbers(
let bg_color = ui.visuals().extreme_bg_color; let bg_color = ui.visuals().extreme_bg_color;
let line_numbers_rect = ui.available_rect_before_wrap(); let line_numbers_rect = ui.available_rect_before_wrap();
ui.painter().rect_filled(line_numbers_rect, 0.0, bg_color); ui.painter()
.rect_filled(line_numbers_rect, 0.0, bg_color);
let font_id = egui::FontId::monospace(font_size); let font_id = egui::FontId::monospace(font_size);
let line_count_width = line_count.to_string().len(); let line_count_width = line_count.to_string().len();

View File

@ -178,12 +178,8 @@ pub(crate) fn menu_bar(app: &mut TextEditor, ctx: &egui::Context) {
app.save_config(); app.save_config();
ui.close_menu(); ui.close_menu();
} }
if ui.checkbox(&mut app.word_wrap, "Word Wrap").clicked() {
app.save_config();
ui.close_menu();
}
if ui if ui
.checkbox(&mut app.auto_hide_tab_bar, "Hide Tab Bar") .checkbox(&mut app.word_wrap, "Toggle Word Wrap")
.clicked() .clicked()
{ {
app.save_config(); app.save_config();
@ -196,6 +192,13 @@ pub(crate) fn menu_bar(app: &mut TextEditor, ctx: &egui::Context) {
app.save_config(); app.save_config();
ui.close_menu(); ui.close_menu();
} }
if ui
.checkbox(&mut app.auto_hide_tab_bar, "Auto Hide Tab Bar")
.clicked()
{
app.save_config();
ui.close_menu();
}
ui.separator(); ui.separator();
@ -279,18 +282,11 @@ pub(crate) fn menu_bar(app: &mut TextEditor, ctx: &egui::Context) {
}; };
let window_width = ctx.screen_rect().width(); let window_width = ctx.screen_rect().width();
let font_id = ui.style().text_styles[&egui::TextStyle::Body].clone();
let tab_title = if app.get_active_tab().is_some_and(|tab| tab.is_modified) {
format!("{tab_title}*")
} else {
tab_title
};
let text_galley = ui.fonts(|fonts| { let text_galley = ui.fonts(|fonts| {
fonts.layout_job(egui::text::LayoutJob::simple_singleline( fonts.layout_job(egui::text::LayoutJob::simple_singleline(
tab_title, tab_title,
font_id, ui.style().text_styles[&egui::TextStyle::Body].clone(),
ui.style().visuals.text_color(), ui.style().visuals.text_color(),
)) ))
}); });

View File

@ -72,11 +72,7 @@ pub(crate) fn preferences_window(app: &mut TextEditor, ctx: &egui::Context) {
app.font_size_input = Some(app.font_size.to_string()); app.font_size_input = Some(app.font_size.to_string());
} }
let mut font_size_text = app let mut font_size_text = app.font_size_input.as_ref().unwrap().clone();
.font_size_input
.as_ref()
.unwrap_or(&"14".to_string())
.clone();
let response = ui.add( let response = ui.add(
egui::TextEdit::singleline(&mut font_size_text) egui::TextEdit::singleline(&mut font_size_text)
.desired_width(50.0) .desired_width(50.0)
@ -127,10 +123,8 @@ pub(crate) fn preferences_window(app: &mut TextEditor, ctx: &egui::Context) {
}, },
); );
ui.label( ui.label(
egui::RichText::new( egui::RichText::new("The quick brown fox jumps over the lazy dog.")
"The quick brown fox jumps over the lazy dog.", .font(preview_font.clone()),
)
.font(preview_font.clone()),
); );
ui.label( ui.label(
egui::RichText::new("ABCDEFGHIJKLMNOPQRSTUVWXYZ") egui::RichText::new("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
@ -140,9 +134,7 @@ pub(crate) fn preferences_window(app: &mut TextEditor, ctx: &egui::Context) {
egui::RichText::new("abcdefghijklmnopqrstuvwxyz") egui::RichText::new("abcdefghijklmnopqrstuvwxyz")
.font(preview_font.clone()), .font(preview_font.clone()),
); );
ui.label( ui.label(egui::RichText::new("1234567890 !@#$%^&*()").font(preview_font));
egui::RichText::new("1234567890 !@#$%^&*()").font(preview_font),
);
}); });
}); });