From c8ce57781ef05bd496d93cc14bb5b4ef236f7be6 Mon Sep 17 00:00:00 2001 From: Penwing Date: Thu, 25 Jan 2024 20:50:56 +0100 Subject: [PATCH] better format --- src/calcifer.rs | 495 +++++++------ src/calcifer/app_base.rs | 377 +++++----- src/calcifer/code_editor/highlighting.rs | 7 +- src/calcifer/code_editor/mod.rs | 800 +++++++++++---------- src/calcifer/code_editor/themes/fantasy.rs | 4 +- src/calcifer/code_editor/themes/mod.rs | 6 +- src/calcifer/code_editor/themes/sonokai.rs | 36 +- src/main.rs | 409 ++++++----- src/tools/confirm.rs | 99 ++- src/tools/mod.rs | 181 ++--- src/tools/search.rs | 395 +++++----- src/tools/settings.rs | 90 ++- src/tools/shortcuts.rs | 66 +- src/tools/tabs.rs | 113 +-- src/tools/terminal.rs | 164 ++--- src/tools/tests.rs | 141 ++-- 16 files changed, 1768 insertions(+), 1615 deletions(-) diff --git a/src/calcifer.rs b/src/calcifer.rs index 15dc4d6..2b7cd57 100644 --- a/src/calcifer.rs +++ b/src/calcifer.rs @@ -1,242 +1,285 @@ use eframe::egui; use egui::{text::CCursor, text_edit::CCursorRange, Rangef}; -use std::{env, path::Path, cmp::max}; +use std::{cmp::max, env, path::Path}; use crate::tools; use crate::Calcifer; -use crate::PATH_ROOT; use crate::MAX_TABS; +use crate::PATH_ROOT; pub mod code_editor; use code_editor::CodeEditor; mod app_base; - impl Calcifer { - pub fn draw_settings(&mut self, ctx: &egui::Context) { - egui::SidePanel::left("settings") - .resizable(false) - .exact_width(self.font_size * 1.8) - .show(ctx, |ui| { - ui.vertical(|ui| { - if ui.add(egui::Button::new("📁")).clicked() { - if let Some(path) = rfd::FileDialog::new().set_directory(Path::new(&PATH_ROOT)).pick_file() { - self.open_file(Some(&path)); - } - } - ui.separator(); - self.tree_visible = self.toggle(ui, self.tree_visible, "🗐"); - ui.separator(); - self.terminal_visible = self.toggle(ui, self.terminal_visible, "🖵"); - ui.separator(); - self.search_menu.visible = self.toggle(ui, self.search_menu.visible, "🔍"); - ui.separator(); - self.settings_menu.visible = self.toggle(ui, self.settings_menu.visible, "⚙"); - ui.separator(); - self.shortcuts_menu.visible = self.toggle(ui, self.shortcuts_menu.visible, "⌨"); - ui.separator(); - self.profiler_visible = self.toggle(ui, self.profiler_visible, "⚡"); - }); - }); - } - - - pub fn draw_tree_panel(&mut self, ctx: &egui::Context) { - if !self.tree_visible { - return - } - egui::SidePanel::left("file_tree_panel").show(ctx, |ui| { - ui.heading("Bookshelf"); - ui.separator(); - let _ = self.list_files(ui, Path::new(&PATH_ROOT)); - ui.separator(); - }); - } - - pub fn draw_bottom_tray(&mut self, ctx: &egui::Context) { - egui::TopBottomPanel::bottom("tray") - .default_height(self.font_size * 1.2) - .resizable(false) - .show(ctx, |ui| { - ui.label(self.profiler()); - }); - } - - - pub fn draw_terminal_panel(&mut self, ctx: &egui::Context) { - if !self.terminal_visible { - return - } - egui::TopBottomPanel::bottom("terminal") - .default_height(super::TERMINAL_HEIGHT.clone()) - .height_range(Rangef::new(super::TERMINAL_RANGE.start, super::TERMINAL_RANGE.end)) - .resizable(true) - .show(ctx, |ui| { - ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| { - let command_color = egui::Color32::from_hex(self.theme.functions).expect("Theme color issue (functions)"); - let entry_color = egui::Color32::from_hex(self.theme.literals).expect("Theme color issue (literals)"); - let bg_color = egui::Color32::from_hex(self.theme.bg).expect("Theme color issue (bg)"); - - ui.label(""); - - ui.horizontal(|ui| { - if ui.add(egui::Button::new("⟳")).clicked() { - self.command_history = vec![]; - } - ui.style_mut().visuals.extreme_bg_color = bg_color; - let Self { command, .. } = self; - ui.colored_label(command_color.clone(), tools::format_path(&env::current_dir().expect("Could not find Shell Environnment"))); - let response = ui.add(egui::TextEdit::singleline(command).desired_width(f32::INFINITY).lock_focus(true)); + pub fn draw_settings(&mut self, ctx: &egui::Context) { + egui::SidePanel::left("settings") + .resizable(false) + .exact_width(self.font_size * 1.8) + .show(ctx, |ui| { + ui.vertical(|ui| { + if ui.add(egui::Button::new("📁")).clicked() { + if let Some(path) = rfd::FileDialog::new() + .set_directory(Path::new(&PATH_ROOT)) + .pick_file() + { + self.open_file(Some(&path)); + } + } + ui.separator(); + self.tree_visible = self.toggle(ui, self.tree_visible, "🗐"); + ui.separator(); + self.terminal_visible = self.toggle(ui, self.terminal_visible, "🖵"); + ui.separator(); + self.search_menu.visible = self.toggle(ui, self.search_menu.visible, "🔍"); + ui.separator(); + self.settings_menu.visible = self.toggle(ui, self.settings_menu.visible, "⚙"); + ui.separator(); + self.shortcuts_menu.visible = self.toggle(ui, self.shortcuts_menu.visible, "⌨"); + ui.separator(); + self.profiler_visible = self.toggle(ui, self.profiler_visible, "⚡"); + }); + }); + } - if response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter)) { - self.command_history.push(tools::send_command(self.command.clone())); - self.command = "".into(); - response.request_focus(); - } - }); - ui.separator(); - egui::ScrollArea::vertical().stick_to_bottom(true).show(ui, |ui| { - ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { - ui.separator(); - ui.horizontal_wrapped(|ui| { - ui.spacing_mut().item_spacing.y = 0.0; - for entry in &mut self.command_history { - entry.update(); - ui.colored_label(command_color, format!("\n{} {}", entry.env, entry.command)); - ui.end_row(); - if entry.output != "" { - ui.colored_label(entry_color, &entry.output); - ui.end_row(); - } - if entry.error != "" { - ui.colored_label(super::RED, &entry.error); - ui.end_row(); - } - } - }); - }); - }); - }); - }); - } - - - pub fn draw_tab_panel(&mut self, ctx: &egui::Context) { - egui::TopBottomPanel::top("tabs") - .resizable(false) - .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.style_mut().visuals.selection.bg_fill = egui::Color32::from_hex(self.theme.functions).expect("Could not convert color"); - ui.style_mut().visuals.hyperlink_color = egui::Color32::from_hex(self.theme.functions).expect("Could not convert color"); - for (index, tab) in self.tabs.clone().iter().enumerate() { - let mut title = tab.get_name(); - if !tab.saved { - title += " ~"; - } - if self.selected_tab == tools::TabNumber::from_index(index) { - ui.style_mut().visuals.override_text_color = Some(egui::Color32::from_hex(self.theme.bg).expect("Could not convert color")); - } - ui.selectable_value(&mut self.selected_tab, tools::TabNumber::from_index(index), title); - - ui.style_mut().visuals.override_text_color = None; - - if ui.link("X").clicked() && !self.close_tab_confirm.visible { - if self.tabs.len() > 1 { - if tab.saved { - self.delete_tab(index); - } else { - self.close_tab_confirm.ask(); - self.tab_to_close = index; - } - } else { - egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close); - } - } - ui.separator(); - } - if self.tabs.len() < MAX_TABS { - ui.selectable_value(&mut self.selected_tab, tools::TabNumber::Open, "+"); - } - if self.selected_tab == tools::TabNumber::Open { - self.open_file(None); - } - }); - }); - } - - - pub fn draw_content_panel(&mut self, ctx: &egui::Context) { - egui::CentralPanel::default().show(ctx, |ui| { - ui.horizontal(|ui| { - if ui.add(egui::Button::new("open in terminal")).clicked() { - let mut path = self.tabs[self.selected_tab.to_index()].path.clone(); - path.pop(); - tools::send_command(format!("cd {}", path.display())); - } - - ui.label("Picked file:"); - ui.monospace(self.tabs[self.selected_tab.to_index()].path.to_string_lossy().to_string()); - }); - - ui.separator(); - - self.draw_code_file(ui); - }); - } - - - fn draw_code_file(&mut self, ui: &mut egui::Ui) { - let current_tab = &mut self.tabs[self.selected_tab.to_index()]; - let lines = current_tab.code.chars().filter(|&c| c == '\n').count() + 1; - let mut override_cursor : Option = None; + pub fn draw_tree_panel(&mut self, ctx: &egui::Context) { + if !self.tree_visible { + return; + } + egui::SidePanel::left("file_tree_panel").show(ctx, |ui| { + ui.heading("Bookshelf"); + ui.separator(); + let _ = self.list_files(ui, Path::new(&PATH_ROOT)); + ui.separator(); + }); + } - if !self.search_menu.result_selected { - override_cursor = Some(CCursorRange::two( - CCursor::new(self.search_menu.get_cursor_start()), - CCursor::new(self.search_menu.get_cursor_end()), - )); - self.search_menu.result_selected = true; - } - - CodeEditor::default().id_source("code editor") - .with_rows(max(45,lines)) - .with_fontsize(self.font_size) - .with_theme(self.theme) - .with_syntax(tools::to_syntax(¤t_tab.language)) - .with_numlines(true) - .show(ui, &mut current_tab.code, &mut current_tab.saved, &mut current_tab.last_cursor, &mut current_tab.scroll_offset, override_cursor); - } - - pub fn draw_windows(&mut self, ctx: &egui::Context) { - if self.search_menu.visible { - self.search_menu.show(ctx, &mut self.tabs, &mut self.selected_tab); - } - if self.close_tab_confirm.visible { - self.close_tab_confirm.show(ctx); - } - if self.refresh_confirm.visible { - self.refresh_confirm.show(ctx); - } - if self.exit_confirm.visible { - self.exit_confirm.show(ctx); - } - if self.exit_confirm.proceed { - for tab in self.tabs.iter_mut() { - tab.saved = true; - } - egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close); - } - if self.shortcuts_menu.visible { - self.shortcuts_menu.show(ctx); - } - if self.settings_menu.visible { - self.settings_menu.show(ctx); - } - if self.settings_menu.updated { - self.theme = self.settings_menu.theme.clone(); - } - - self.handle_confirm(); - } + pub fn draw_bottom_tray(&mut self, ctx: &egui::Context) { + egui::TopBottomPanel::bottom("tray") + .default_height(self.font_size * 1.2) + .resizable(false) + .show(ctx, |ui| { + ui.label(self.profiler()); + }); + } + + pub fn draw_terminal_panel(&mut self, ctx: &egui::Context) { + if !self.terminal_visible { + return; + } + egui::TopBottomPanel::bottom("terminal") + .default_height(super::TERMINAL_HEIGHT.clone()) + .height_range(Rangef::new( + super::TERMINAL_RANGE.start, + super::TERMINAL_RANGE.end, + )) + .resizable(true) + .show(ctx, |ui| { + ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| { + let command_color = egui::Color32::from_hex(self.theme.functions) + .expect("Theme color issue (functions)"); + let entry_color = egui::Color32::from_hex(self.theme.literals) + .expect("Theme color issue (literals)"); + let bg_color = + egui::Color32::from_hex(self.theme.bg).expect("Theme color issue (bg)"); + + ui.label(""); + + ui.horizontal(|ui| { + if ui.add(egui::Button::new("⟳")).clicked() { + self.command_history = vec![]; + } + ui.style_mut().visuals.extreme_bg_color = bg_color; + let Self { command, .. } = self; + ui.colored_label( + command_color.clone(), + tools::format_path( + &env::current_dir().expect("Could not find Shell Environnment"), + ), + ); + let response = ui.add( + egui::TextEdit::singleline(command) + .desired_width(f32::INFINITY) + .lock_focus(true), + ); + + if response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter)) { + self.command_history + .push(tools::send_command(self.command.clone())); + self.command = "".into(); + response.request_focus(); + } + }); + ui.separator(); + egui::ScrollArea::vertical() + .stick_to_bottom(true) + .show(ui, |ui| { + ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| { + ui.separator(); + ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing.y = 0.0; + for entry in &mut self.command_history { + entry.update(); + ui.colored_label( + command_color, + format!("\n{} {}", entry.env, entry.command), + ); + ui.end_row(); + if entry.output != "" { + ui.colored_label(entry_color, &entry.output); + ui.end_row(); + } + if entry.error != "" { + ui.colored_label(super::RED, &entry.error); + ui.end_row(); + } + } + }); + }); + }); + }); + }); + } + + pub fn draw_tab_panel(&mut self, ctx: &egui::Context) { + egui::TopBottomPanel::top("tabs") + .resizable(false) + .show(ctx, |ui| { + ui.horizontal(|ui| { + ui.style_mut().visuals.selection.bg_fill = + egui::Color32::from_hex(self.theme.functions) + .expect("Could not convert color"); + ui.style_mut().visuals.hyperlink_color = + egui::Color32::from_hex(self.theme.functions) + .expect("Could not convert color"); + for (index, tab) in self.tabs.clone().iter().enumerate() { + let mut title = tab.get_name(); + if !tab.saved { + title += " ~"; + } + if self.selected_tab == tools::TabNumber::from_index(index) { + ui.style_mut().visuals.override_text_color = Some( + egui::Color32::from_hex(self.theme.bg) + .expect("Could not convert color"), + ); + } + ui.selectable_value( + &mut self.selected_tab, + tools::TabNumber::from_index(index), + title, + ); + + ui.style_mut().visuals.override_text_color = None; + + if ui.link("X").clicked() && !self.close_tab_confirm.visible { + if self.tabs.len() > 1 { + if tab.saved { + self.delete_tab(index); + } else { + self.close_tab_confirm.ask(); + self.tab_to_close = index; + } + } else { + egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close); + } + } + ui.separator(); + } + if self.tabs.len() < MAX_TABS { + ui.selectable_value(&mut self.selected_tab, tools::TabNumber::Open, "+"); + } + if self.selected_tab == tools::TabNumber::Open { + self.open_file(None); + } + }); + }); + } + + pub fn draw_content_panel(&mut self, ctx: &egui::Context) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.horizontal(|ui| { + if ui.add(egui::Button::new("open in terminal")).clicked() { + let mut path = self.tabs[self.selected_tab.to_index()].path.clone(); + path.pop(); + tools::send_command(format!("cd {}", path.display())); + } + + ui.label("Picked file:"); + ui.monospace( + self.tabs[self.selected_tab.to_index()] + .path + .to_string_lossy() + .to_string(), + ); + }); + + ui.separator(); + + self.draw_code_file(ui); + }); + } + + fn draw_code_file(&mut self, ui: &mut egui::Ui) { + let current_tab = &mut self.tabs[self.selected_tab.to_index()]; + let lines = current_tab.code.chars().filter(|&c| c == '\n').count() + 1; + let mut override_cursor: Option = None; + + if !self.search_menu.result_selected { + override_cursor = Some(CCursorRange::two( + CCursor::new(self.search_menu.get_cursor_start()), + CCursor::new(self.search_menu.get_cursor_end()), + )); + self.search_menu.result_selected = true; + } + + CodeEditor::default() + .id_source("code editor") + .with_rows(max(45, lines)) + .with_fontsize(self.font_size) + .with_theme(self.theme) + .with_syntax(tools::to_syntax(¤t_tab.language)) + .with_numlines(true) + .show( + ui, + &mut current_tab.code, + &mut current_tab.saved, + &mut current_tab.last_cursor, + &mut current_tab.scroll_offset, + override_cursor, + ); + } + + pub fn draw_windows(&mut self, ctx: &egui::Context) { + if self.search_menu.visible { + self.search_menu + .show(ctx, &mut self.tabs, &mut self.selected_tab); + } + if self.close_tab_confirm.visible { + self.close_tab_confirm.show(ctx); + } + if self.refresh_confirm.visible { + self.refresh_confirm.show(ctx); + } + if self.exit_confirm.visible { + self.exit_confirm.show(ctx); + } + if self.exit_confirm.proceed { + for tab in self.tabs.iter_mut() { + tab.saved = true; + } + egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::Close); + } + if self.shortcuts_menu.visible { + self.shortcuts_menu.show(ctx); + } + if self.settings_menu.visible { + self.settings_menu.show(ctx); + } + if self.settings_menu.updated { + self.theme = self.settings_menu.theme.clone(); + } + + self.handle_confirm(); + } } diff --git a/src/calcifer/app_base.rs b/src/calcifer/app_base.rs index ac59b83..73b91bc 100644 --- a/src/calcifer/app_base.rs +++ b/src/calcifer/app_base.rs @@ -1,188 +1,213 @@ -use std::{path::PathBuf, fs, path::Path, cmp::min, io}; use eframe::egui; use egui::Color32; +use std::{cmp::min, fs, io, path::Path, path::PathBuf}; -use crate::Calcifer; use crate::tools; -use crate::PATH_ROOT; +use crate::Calcifer; use crate::DEFAULT_THEMES; use crate::MAX_TABS; +use crate::PATH_ROOT; use crate::SAVE_PATH; use crate::TIME_LABELS; - impl Calcifer { - pub fn handle_confirm(&mut self) { - if self.close_tab_confirm.proceed { - self.close_tab_confirm.close(); - self.delete_tab(self.tab_to_close); - } - - if self.refresh_confirm.proceed { - self.refresh_confirm.close(); - self.tabs[self.selected_tab.to_index()].refresh(); - } - } - - pub fn save_tab(&self) -> Option { - if self.tabs[self.selected_tab.to_index()].path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string() == "untitled" { - return self.save_tab_as(); - } else { - if let Err(err) = fs::write(&self.tabs[self.selected_tab.to_index()].path, &self.tabs[self.selected_tab.to_index()].code) { - eprintln!("Error writing file: {}", err); - return None; - } - return Some(self.tabs[self.selected_tab.to_index()].path.clone()) - } - } - - - pub fn save_tab_as(&self) -> Option { - if let Some(path) = rfd::FileDialog::new().set_directory(Path::new(&PATH_ROOT)).save_file() { - if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab.to_index()].code) { - eprintln!("Error writing file: {}", err); - return None; - } - return Some(path); - } - return None - } - - - pub fn handle_save_file(&mut self, path_option : Option) { - if let Some(path) = path_option { - println!("File saved successfully at: {:?}", path); - self.tabs[self.selected_tab.to_index()].path = path; - self.tabs[self.selected_tab.to_index()].saved = true; - } else { - println!("File save failed."); - } - } - - - pub fn from_app_state(app_state: tools::AppState) -> Self { - let mut new = Self { - theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)], - tabs: Vec::new(), - settings_menu: tools::settings::SettingsWindow::new(DEFAULT_THEMES[app_state.theme]), - ..Default::default() - }; - - for path in app_state.tabs { - if path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string() != "untitled" { - new.open_file(Some(&path)); - } - } - - if new.tabs == vec![] { - new.open_file(None); - } - - new - } - - - pub fn save_state(&self) { - let mut state_theme : usize = 0; - if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) { - state_theme = theme; - } - - let mut state_tabs = vec![]; - - for tab in &self.tabs { - state_tabs.push(tab.path.clone()); - } - let app_state = tools::AppState { - tabs: state_tabs, - theme: state_theme, - }; - - let _ = tools::save_state(&app_state, SAVE_PATH); - } - - - pub fn move_through_tabs(&mut self, forward : bool) { - let new_index = if forward { - (self.selected_tab.to_index() + 1) % self.tabs.len() - } else { - self.selected_tab.to_index().checked_sub(1).unwrap_or(self.tabs.len() - 1) - }; - self.selected_tab = tools::TabNumber::from_index(new_index); - } - - - pub fn list_files(&mut self, ui: &mut egui::Ui, path: &Path) -> io::Result<()> { - if let Some(name) = path.file_name() { - if path.is_dir() { - egui::CollapsingHeader::new(name.to_string_lossy()).show(ui, |ui| { - let mut paths: Vec<_> = fs::read_dir(&path).expect("Failed to read dir").map(|r| r.unwrap()).collect(); - - paths.sort_by(|a, b| tools::sort_directories_first(a, b)); + pub fn handle_confirm(&mut self) { + if self.close_tab_confirm.proceed { + self.close_tab_confirm.close(); + self.delete_tab(self.tab_to_close); + } - for result in paths { - let _ = self.list_files(ui, &result.path()); - } - }); - } else { - if ui.button(name.to_string_lossy()).clicked() { - self.open_file(Some(path)); - } - } - } - Ok(()) - } - - - pub fn open_file(&mut self, path_option: Option<&Path>) { - if self.tabs.len() < MAX_TABS { - if let Some(path) = path_option { - self.tabs.push(tools::Tab::new(path.to_path_buf())); - } else { - self.tabs.push(tools::Tab::default()); - } - self.selected_tab = tools::TabNumber::from_index(self.tabs.len() - 1); - } - } - - - pub fn delete_tab(&mut self, index : usize) { - self.tabs.remove(index); - self.selected_tab = tools::TabNumber::from_index(min(index, self.tabs.len() - 1)); - } - - - pub fn toggle(&self, ui: &mut egui::Ui, display : bool, title : &str) -> bool { - let bg_color : Color32; - let text_color : Color32; - - if display.clone() { - bg_color = Color32::from_hex(self.theme.functions).expect("Could not convert color to hex (functions)"); - text_color = Color32::from_hex(self.theme.bg).expect("Could not convert color to hex (bg)"); - } else { - bg_color = Color32::from_hex(self.theme.bg).expect("Could not convert color to hex (bg)"); - text_color = Color32::from_hex(self.theme.literals).expect("Could not convert color to hex (literals)"); - }; - - ui.style_mut().visuals.override_text_color = Some(text_color); - - if ui.add(egui::Button::new(title).fill(bg_color)).clicked() { - return !display - } - ui.style_mut().visuals.override_text_color = None; - - return display - } - - pub fn profiler(&self) -> String { - if !self.profiler_visible { - return "".to_string() - } - let combined_string: Vec = TIME_LABELS.into_iter().zip(self.time_watch.clone().into_iter()) - .map(|(s, v)| format!("{} : {:.1} ms", s, v)).collect(); + if self.refresh_confirm.proceed { + self.refresh_confirm.close(); + self.tabs[self.selected_tab.to_index()].refresh(); + } + } - let mut result = combined_string.join(" ; "); - result.push_str(&format!(" total : {:.1} ms", self.time_watch.clone().iter().sum::())); - return result - } -} \ No newline at end of file + pub fn save_tab(&self) -> Option { + if self.tabs[self.selected_tab.to_index()] + .path + .file_name() + .expect("Could not get Tab Name") + .to_string_lossy() + .to_string() + == "untitled" + { + return self.save_tab_as(); + } else { + if let Err(err) = fs::write( + &self.tabs[self.selected_tab.to_index()].path, + &self.tabs[self.selected_tab.to_index()].code, + ) { + eprintln!("Error writing file: {}", err); + return None; + } + return Some(self.tabs[self.selected_tab.to_index()].path.clone()); + } + } + + pub fn save_tab_as(&self) -> Option { + if let Some(path) = rfd::FileDialog::new() + .set_directory(Path::new(&PATH_ROOT)) + .save_file() + { + if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab.to_index()].code) { + eprintln!("Error writing file: {}", err); + return None; + } + return Some(path); + } + return None; + } + + pub fn handle_save_file(&mut self, path_option: Option) { + if let Some(path) = path_option { + println!("File saved successfully at: {:?}", path); + self.tabs[self.selected_tab.to_index()].path = path; + self.tabs[self.selected_tab.to_index()].saved = true; + } else { + println!("File save failed."); + } + } + + pub fn from_app_state(app_state: tools::AppState) -> Self { + let mut new = Self { + theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)], + tabs: Vec::new(), + settings_menu: tools::settings::SettingsWindow::new(DEFAULT_THEMES[app_state.theme]), + ..Default::default() + }; + + for path in app_state.tabs { + if path + .file_name() + .expect("Could not get Tab Name") + .to_string_lossy() + .to_string() + != "untitled" + { + new.open_file(Some(&path)); + } + } + + if new.tabs == vec![] { + new.open_file(None); + } + + new + } + + pub fn save_state(&self) { + let mut state_theme: usize = 0; + if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) { + state_theme = theme; + } + + let mut state_tabs = vec![]; + + for tab in &self.tabs { + state_tabs.push(tab.path.clone()); + } + let app_state = tools::AppState { + tabs: state_tabs, + theme: state_theme, + }; + + let _ = tools::save_state(&app_state, SAVE_PATH); + } + + pub fn move_through_tabs(&mut self, forward: bool) { + let new_index = if forward { + (self.selected_tab.to_index() + 1) % self.tabs.len() + } else { + self.selected_tab + .to_index() + .checked_sub(1) + .unwrap_or(self.tabs.len() - 1) + }; + self.selected_tab = tools::TabNumber::from_index(new_index); + } + + pub fn list_files(&mut self, ui: &mut egui::Ui, path: &Path) -> io::Result<()> { + if let Some(name) = path.file_name() { + if path.is_dir() { + egui::CollapsingHeader::new(name.to_string_lossy()).show(ui, |ui| { + let mut paths: Vec<_> = fs::read_dir(&path) + .expect("Failed to read dir") + .map(|r| r.unwrap()) + .collect(); + + paths.sort_by(|a, b| tools::sort_directories_first(a, b)); + + for result in paths { + let _ = self.list_files(ui, &result.path()); + } + }); + } else { + if ui.button(name.to_string_lossy()).clicked() { + self.open_file(Some(path)); + } + } + } + Ok(()) + } + + pub fn open_file(&mut self, path_option: Option<&Path>) { + if self.tabs.len() < MAX_TABS { + if let Some(path) = path_option { + self.tabs.push(tools::Tab::new(path.to_path_buf())); + } else { + self.tabs.push(tools::Tab::default()); + } + self.selected_tab = tools::TabNumber::from_index(self.tabs.len() - 1); + } + } + + pub fn delete_tab(&mut self, index: usize) { + self.tabs.remove(index); + self.selected_tab = tools::TabNumber::from_index(min(index, self.tabs.len() - 1)); + } + + pub fn toggle(&self, ui: &mut egui::Ui, display: bool, title: &str) -> bool { + let bg_color: Color32; + let text_color: Color32; + + if display.clone() { + bg_color = Color32::from_hex(self.theme.functions) + .expect("Could not convert color to hex (functions)"); + text_color = + Color32::from_hex(self.theme.bg).expect("Could not convert color to hex (bg)"); + } else { + bg_color = + Color32::from_hex(self.theme.bg).expect("Could not convert color to hex (bg)"); + text_color = Color32::from_hex(self.theme.literals) + .expect("Could not convert color to hex (literals)"); + }; + + ui.style_mut().visuals.override_text_color = Some(text_color); + + if ui.add(egui::Button::new(title).fill(bg_color)).clicked() { + return !display; + } + ui.style_mut().visuals.override_text_color = None; + + return display; + } + + pub fn profiler(&self) -> String { + if !self.profiler_visible { + return "".to_string(); + } + let combined_string: Vec = TIME_LABELS + .into_iter() + .zip(self.time_watch.clone().into_iter()) + .map(|(s, v)| format!("{} : {:.1} ms", s, v)) + .collect(); + + let mut result = combined_string.join(" ; "); + result.push_str(&format!( + " total : {:.1} ms", + self.time_watch.clone().iter().sum::() + )); + return result; + } +} diff --git a/src/calcifer/code_editor/highlighting.rs b/src/calcifer/code_editor/highlighting.rs index a1a779c..7f5b257 100644 --- a/src/calcifer/code_editor/highlighting.rs +++ b/src/calcifer/code_editor/highlighting.rs @@ -1,8 +1,8 @@ use super::syntax::{Syntax, TokenType, QUOTES, SEPARATORS}; -use std::mem; use super::CodeEditor; -use eframe::egui::text::LayoutJob; use eframe::egui; +use eframe::egui::text::LayoutJob; +use std::mem; #[derive(Default, Debug, PartialEq, PartialOrd, Eq, Ord)] /// Lexer and Token @@ -222,8 +222,6 @@ impl Token { } } - - impl eframe::egui::util::cache::ComputerMut<(&CodeEditor, &str), LayoutJob> for Token { fn compute(&mut self, (cache, text): (&CodeEditor, &str)) -> LayoutJob { self.highlight(cache, text) @@ -236,7 +234,6 @@ pub fn highlight(ctx: &egui::Context, cache: &CodeEditor, text: &str) -> LayoutJ ctx.memory_mut(|mem| mem.caches.cache::().get((cache, text))) } - impl CodeEditor { fn append(&self, job: &mut LayoutJob, token: &Token) { job.append(token.buffer(), 0.0, self.format(token.ty())); diff --git a/src/calcifer/code_editor/mod.rs b/src/calcifer/code_editor/mod.rs index 0bfcea0..f1019f0 100644 --- a/src/calcifer/code_editor/mod.rs +++ b/src/calcifer/code_editor/mod.rs @@ -5,436 +5,502 @@ mod syntax; pub mod themes; use eframe::egui; -use egui::{text_edit::CCursorRange, text::CCursor}; +use egui::{text::CCursor, text_edit::CCursorRange}; use highlighting::highlight; +use std::cmp::{max, min}; use std::hash::{Hash, Hasher}; +use std::ops::{Bound, RangeBounds}; pub use syntax::{Syntax, TokenType}; pub use themes::ColorTheme; -use std::cmp::{min, max}; -use std::ops::{Bound, RangeBounds}; - trait StringUtils { - fn substring(&self, start: usize, len: usize) -> &str; - fn slice(&self, range: impl RangeBounds) -> &str; - fn char_at(&self, index: usize) -> char; + fn substring(&self, start: usize, len: usize) -> &str; + fn slice(&self, range: impl RangeBounds) -> &str; + fn char_at(&self, index: usize) -> char; } impl StringUtils for str { - fn substring(&self, start: usize, len: usize) -> &str { - let mut char_pos = 0; - let mut byte_start = 0; - let mut it = self.chars(); - loop { - if char_pos == start { break; } - if let Some(c) = it.next() { - char_pos += 1; - byte_start += c.len_utf8(); - } - else { break; } - } - char_pos = 0; - let mut byte_end = byte_start; - loop { - if char_pos == len { break; } - if let Some(c) = it.next() { - char_pos += 1; - byte_end += c.len_utf8(); - } - else { break; } - } - &self[byte_start..byte_end] - } + fn substring(&self, start: usize, len: usize) -> &str { + let mut char_pos = 0; + let mut byte_start = 0; + let mut it = self.chars(); + loop { + if char_pos == start { + break; + } + if let Some(c) = it.next() { + char_pos += 1; + byte_start += c.len_utf8(); + } else { + break; + } + } + char_pos = 0; + let mut byte_end = byte_start; + loop { + if char_pos == len { + break; + } + if let Some(c) = it.next() { + char_pos += 1; + byte_end += c.len_utf8(); + } else { + break; + } + } + &self[byte_start..byte_end] + } - fn slice(&self, range: impl RangeBounds) -> &str { - let start = match range.start_bound() { - Bound::Included(bound) | Bound::Excluded(bound) => *bound, - Bound::Unbounded => 0, - }; - let len = match range.end_bound() { - Bound::Included(bound) => *bound + 1, - Bound::Excluded(bound) => *bound, - Bound::Unbounded => self.len(), - } - start; - self.substring(start, len) - } - - fn char_at(&self, index: usize) -> char { - self.chars().nth(index).unwrap_or('\0') // '\0' is used as the default value - } + fn slice(&self, range: impl RangeBounds) -> &str { + let start = match range.start_bound() { + Bound::Included(bound) | Bound::Excluded(bound) => *bound, + Bound::Unbounded => 0, + }; + let len = match range.end_bound() { + Bound::Included(bound) => *bound + 1, + Bound::Excluded(bound) => *bound, + Bound::Unbounded => self.len(), + } - start; + self.substring(start, len) + } + + fn char_at(&self, index: usize) -> char { + self.chars().nth(index).unwrap_or('\0') // '\0' is used as the default value + } } - #[derive(Clone, Debug, PartialEq)] /// CodeEditor struct which stores settings for highlighting. pub struct CodeEditor { - id: String, - theme: ColorTheme, - syntax: Syntax, - numlines: bool, - fontsize: f32, - rows: usize, - vscroll: bool, - stick_to_bottom: bool, - shrink: bool, + id: String, + theme: ColorTheme, + syntax: Syntax, + numlines: bool, + fontsize: f32, + rows: usize, + vscroll: bool, + stick_to_bottom: bool, + shrink: bool, } impl Hash for CodeEditor { - fn hash(&self, state: &mut H) { - self.theme.hash(state); - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - (self.fontsize as u32).hash(state); - self.syntax.hash(state); - } + fn hash(&self, state: &mut H) { + self.theme.hash(state); + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + (self.fontsize as u32).hash(state); + self.syntax.hash(state); + } } impl Default for CodeEditor { - fn default() -> CodeEditor { - CodeEditor { - id: String::from("Code Editor"), - theme: ColorTheme::GRUVBOX, - syntax: Syntax::rust(), - numlines: true, - fontsize: 10.0, - rows: 10, - vscroll: true, - stick_to_bottom: false, - shrink: false, - } - } + fn default() -> CodeEditor { + CodeEditor { + id: String::from("Code Editor"), + theme: ColorTheme::GRUVBOX, + syntax: Syntax::rust(), + numlines: true, + fontsize: 10.0, + rows: 10, + vscroll: true, + stick_to_bottom: false, + shrink: false, + } + } } impl CodeEditor { - pub fn id_source(self, id_source: impl Into) -> Self { - CodeEditor { - id: id_source.into(), - ..self - } - } + pub fn id_source(self, id_source: impl Into) -> Self { + CodeEditor { + id: id_source.into(), + ..self + } + } - /// Minimum number of rows to show. - /// - /// **Default: 10** - pub fn with_rows(self, rows: usize) -> Self { - CodeEditor { rows, ..self } - } + /// Minimum number of rows to show. + /// + /// **Default: 10** + pub fn with_rows(self, rows: usize) -> Self { + CodeEditor { rows, ..self } + } - /// Use custom Color Theme - /// - /// **Default: Gruvbox** - pub fn with_theme(self, theme: ColorTheme) -> Self { - CodeEditor { theme, ..self } - } + /// Use custom Color Theme + /// + /// **Default: Gruvbox** + pub fn with_theme(self, theme: ColorTheme) -> Self { + CodeEditor { theme, ..self } + } - /// Use custom font size - /// - /// **Default: 10.0** - pub fn with_fontsize(self, fontsize: f32) -> Self { - CodeEditor { fontsize, ..self } - } + /// Use custom font size + /// + /// **Default: 10.0** + pub fn with_fontsize(self, fontsize: f32) -> Self { + CodeEditor { fontsize, ..self } + } - #[cfg(feature = "egui")] - /// Use UI font size - pub fn with_ui_fontsize(self, ui: &mut egui::Ui) -> Self { - CodeEditor { - fontsize: egui::TextStyle::Monospace.resolve(ui.style()).size, - ..self - } - } + #[cfg(feature = "egui")] + /// Use UI font size + pub fn with_ui_fontsize(self, ui: &mut egui::Ui) -> Self { + CodeEditor { + fontsize: egui::TextStyle::Monospace.resolve(ui.style()).size, + ..self + } + } - /// Show or hide lines numbering - /// - /// **Default: true** - pub fn with_numlines(self, numlines: bool) -> Self { - CodeEditor { numlines, ..self } - } + /// Show or hide lines numbering + /// + /// **Default: true** + pub fn with_numlines(self, numlines: bool) -> Self { + CodeEditor { numlines, ..self } + } - /// Use custom syntax for highlighting - /// - /// **Default: Rust** - pub fn with_syntax(self, syntax: Syntax) -> Self { - CodeEditor { syntax, ..self } - } + /// Use custom syntax for highlighting + /// + /// **Default: Rust** + pub fn with_syntax(self, syntax: Syntax) -> Self { + CodeEditor { syntax, ..self } + } - /// Turn on/off scrolling on the vertical axis. - /// - /// **Default: true** - pub fn vscroll(self, vscroll: bool) -> Self { - CodeEditor { vscroll, ..self } - } - /// Should the containing area shrink if the content is small? - /// - /// **Default: false** - pub fn auto_shrink(self, shrink: bool) -> Self { - CodeEditor { shrink, ..self } - } + /// Turn on/off scrolling on the vertical axis. + /// + /// **Default: true** + pub fn vscroll(self, vscroll: bool) -> Self { + CodeEditor { vscroll, ..self } + } + /// Should the containing area shrink if the content is small? + /// + /// **Default: false** + pub fn auto_shrink(self, shrink: bool) -> Self { + CodeEditor { shrink, ..self } + } - /// Stick to bottom - /// The scroll handle will stick to the bottom position even while the content size - /// changes dynamically. This can be useful to simulate terminal UIs or log/info scrollers. - /// The scroll handle remains stuck until user manually changes position. Once "unstuck" - /// it will remain focused on whatever content viewport the user left it on. If the scroll - /// handle is dragged to the bottom it will again become stuck and remain there until manually - /// pulled from the end position. - /// - /// **Default: false** - pub fn stick_to_bottom(self, stick_to_bottom: bool) -> Self { - CodeEditor { - stick_to_bottom, - ..self - } - } + /// Stick to bottom + /// The scroll handle will stick to the bottom position even while the content size + /// changes dynamically. This can be useful to simulate terminal UIs or log/info scrollers. + /// The scroll handle remains stuck until user manually changes position. Once "unstuck" + /// it will remain focused on whatever content viewport the user left it on. If the scroll + /// handle is dragged to the bottom it will again become stuck and remain there until manually + /// pulled from the end position. + /// + /// **Default: false** + pub fn stick_to_bottom(self, stick_to_bottom: bool) -> Self { + CodeEditor { + stick_to_bottom, + ..self + } + } - pub fn format(&self, ty: TokenType) -> egui::text::TextFormat { - let font_id = egui::FontId::monospace(self.fontsize); - let color = self.theme.type_color(ty); - egui::text::TextFormat::simple(font_id, color) - } + pub fn format(&self, ty: TokenType) -> egui::text::TextFormat { + let font_id = egui::FontId::monospace(self.fontsize); + let color = self.theme.type_color(ty); + egui::text::TextFormat::simple(font_id, color) + } - fn numlines_show(&self, ui: &mut egui::Ui, text: &str) { - let total = if text.ends_with('\n') || text.is_empty() { - text.lines().count() + 1 - } else { - text.lines().count() - } - .max(self.rows); - let max_indent = total.to_string().len(); - let mut counter = (1..=total) - .map(|i| { - let label = i.to_string(); - format!( - "{}{label}", - " ".repeat(max_indent.saturating_sub(label.len())) - ) - }) - .collect::>() - .join("\n"); + fn numlines_show(&self, ui: &mut egui::Ui, text: &str) { + let total = if text.ends_with('\n') || text.is_empty() { + text.lines().count() + 1 + } else { + text.lines().count() + } + .max(self.rows); + let max_indent = total.to_string().len(); + let mut counter = (1..=total) + .map(|i| { + let label = i.to_string(); + format!( + "{}{label}", + " ".repeat(max_indent.saturating_sub(label.len())) + ) + }) + .collect::>() + .join("\n"); - #[allow(clippy::cast_precision_loss)] - let width = max_indent as f32 * self.fontsize * 0.5; + #[allow(clippy::cast_precision_loss)] + let width = max_indent as f32 * self.fontsize * 0.5; - let mut layouter = |ui: &egui::Ui, string: &str, _wrap_width: f32| { - let layout_job = egui::text::LayoutJob::single_section( - string.to_string(), - egui::TextFormat::simple( - egui::FontId::monospace(self.fontsize), - self.theme.type_color(TokenType::Comment(true)), - ), - ); - ui.fonts(|f| f.layout_job(layout_job)) - }; + let mut layouter = |ui: &egui::Ui, string: &str, _wrap_width: f32| { + let layout_job = egui::text::LayoutJob::single_section( + string.to_string(), + egui::TextFormat::simple( + egui::FontId::monospace(self.fontsize), + self.theme.type_color(TokenType::Comment(true)), + ), + ); + ui.fonts(|f| f.layout_job(layout_job)) + }; - ui.add( - egui::TextEdit::multiline(&mut counter) - .id_source(format!("{}_numlines", self.id)) - .font(egui::TextStyle::Monospace) - .interactive(false) - .frame(false) - .desired_rows(self.rows) - .desired_width(width) - .layouter(&mut layouter), - ); - } + ui.add( + egui::TextEdit::multiline(&mut counter) + .id_source(format!("{}_numlines", self.id)) + .font(egui::TextStyle::Monospace) + .interactive(false) + .frame(false) + .desired_rows(self.rows) + .desired_width(width) + .layouter(&mut layouter), + ); + } - /// Show Code Editor - pub fn show(&mut self, ui: &mut egui::Ui, text: &mut String, saved: &mut bool, last_cursor: &mut Option, vertical_offset: &mut f32, override_cursor: Option) { - //let mut text_edit_output: Option = None; - let mut code_editor = |ui: &mut egui::Ui| { - ui.horizontal_top(|h| { - self.theme.modify_style(h, self.fontsize); - if self.numlines { - self.numlines_show(h, text); - } - egui::ScrollArea::horizontal() - .id_source(format!("{}_inner_scroll", self.id)) - .show(h, |ui| { - let mut layouter = |ui: &egui::Ui, string: &str, _wrap_width: f32| { - let layout_job = highlight(ui.ctx(), self, string); - ui.fonts(|f| f.layout_job(layout_job)) - }; + /// Show Code Editor + pub fn show( + &mut self, + ui: &mut egui::Ui, + text: &mut String, + saved: &mut bool, + last_cursor: &mut Option, + vertical_offset: &mut f32, + override_cursor: Option, + ) { + //let mut text_edit_output: Option = None; + let mut code_editor = |ui: &mut egui::Ui| { + ui.horizontal_top(|h| { + self.theme.modify_style(h, self.fontsize); + if self.numlines { + self.numlines_show(h, text); + } + egui::ScrollArea::horizontal() + .id_source(format!("{}_inner_scroll", self.id)) + .show(h, |ui| { + let mut layouter = |ui: &egui::Ui, string: &str, _wrap_width: f32| { + let layout_job = highlight(ui.ctx(), self, string); + ui.fonts(|f| f.layout_job(layout_job)) + }; - let previous_text = text.clone(); + let previous_text = text.clone(); - let mut output = egui::TextEdit::multiline(text) - .id_source(&self.id) - .lock_focus(true) - .desired_rows(self.rows) - .frame(true) - .desired_width(if self.shrink { 0.0 } else { f32::MAX }) - .layouter(&mut layouter) - .show(ui); + let mut output = egui::TextEdit::multiline(text) + .id_source(&self.id) + .lock_focus(true) + .desired_rows(self.rows) + .frame(true) + .desired_width(if self.shrink { 0.0 } else { f32::MAX }) + .layouter(&mut layouter) + .show(ui); - let mut get_new_cursor : bool = true; - let mut extend : isize = 0; + let mut get_new_cursor: bool = true; + let mut extend: isize = 0; - if output.response.has_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { - if let Some(range) = last_cursor { - (*text, extend) = self.new_line(range.clone(), text.clone()); - get_new_cursor = false; - } - } + if output.response.has_focus() + && ui.input(|i| i.key_pressed(egui::Key::Enter)) + { + if let Some(range) = last_cursor { + (*text, extend) = self.new_line(range.clone(), text.clone()); + get_new_cursor = false; + } + } - if output.response.has_focus() && ui.input(|i| i.key_pressed(egui::Key::E) && i.modifiers.ctrl) { - if let Some(range) = last_cursor { - (*text, extend) = self.toggle_start_of_line(range.clone(), text.clone(), "//"); - get_new_cursor = false; - } - } + if output.response.has_focus() + && ui.input(|i| i.key_pressed(egui::Key::E) && i.modifiers.ctrl) + { + if let Some(range) = last_cursor { + (*text, extend) = + self.toggle_start_of_line(range.clone(), text.clone(), "//"); + get_new_cursor = false; + } + } - if output.response.has_focus() && ui.input(|i| i.key_pressed(egui::Key::Tab)) { - if let Some(range) = last_cursor { - if range.primary.index != range.secondary.index { - (*text, extend) = self.add_start_of_line(range.clone(), previous_text.clone(), "\t"); - get_new_cursor = false; - } - } - } + if output.response.has_focus() + && ui.input(|i| i.key_pressed(egui::Key::Tab)) + { + if let Some(range) = last_cursor { + if range.primary.index != range.secondary.index { + (*text, extend) = self.add_start_of_line( + range.clone(), + previous_text.clone(), + "\t", + ); + get_new_cursor = false; + } + } + } - if output.response.has_focus() && ui.input(|i| i.key_pressed(egui::Key::Tab) && i.modifiers.shift) { - if let Some(range) = last_cursor { - if range.primary.index != range.secondary.index { - (*text, extend) = self.remove_start_of_line(range.clone(), previous_text.clone(), "\t"); - get_new_cursor = false; - } - } - } + if output.response.has_focus() + && ui.input(|i| i.key_pressed(egui::Key::Tab) && i.modifiers.shift) + { + if let Some(range) = last_cursor { + if range.primary.index != range.secondary.index { + (*text, extend) = self.remove_start_of_line( + range.clone(), + previous_text.clone(), + "\t", + ); + get_new_cursor = false; + } + } + } - if override_cursor != None { - output.response.request_focus(); - output.state.set_ccursor_range(override_cursor); - output.state.store(ui.ctx(), output.response.id); - } else if get_new_cursor { - *last_cursor = output.state.clone().ccursor_range(); - } else { - if let Some(cursor_range) = last_cursor.clone() { - let mut start = min(cursor_range.primary.index, cursor_range.secondary.index); - let end = max(cursor_range.primary.index, cursor_range.secondary.index); - let extended = match end as isize + extend { - // Check for overflow or negative result - value if value < 0 => 0, - value => value as usize, - }; - if start == end { - start = extended; - } - let cursor = Some(CCursorRange { - primary : CCursor::new(start.clone()), - secondary : CCursor::new(max(start.clone(), extended)), - }); - output.state.set_ccursor_range(cursor.clone()); - output.state.store(ui.ctx(), output.response.id); - *last_cursor = cursor.clone(); - } - } - - if previous_text != text.clone() { - *saved = false; - } - //text_edit_output = Some(output); - }); - }); - }; - if self.vscroll { - let scroll_area = egui::ScrollArea::vertical() - .id_source(format!("{}_outer_scroll", self.id)) - .stick_to_bottom(self.stick_to_bottom) - .vertical_scroll_offset(vertical_offset.clone()) - .show(ui, code_editor); - *vertical_offset = scroll_area.state.offset.y.clone(); - } else { - code_editor(ui); - } + if override_cursor != None { + output.response.request_focus(); + output.state.set_ccursor_range(override_cursor); + output.state.store(ui.ctx(), output.response.id); + } else if get_new_cursor { + *last_cursor = output.state.clone().ccursor_range(); + } else { + if let Some(cursor_range) = last_cursor.clone() { + let mut start = + min(cursor_range.primary.index, cursor_range.secondary.index); + let end = + max(cursor_range.primary.index, cursor_range.secondary.index); + let extended = match end as isize + extend { + // Check for overflow or negative result + value if value < 0 => 0, + value => value as usize, + }; + if start == end { + start = extended; + } + let cursor = Some(CCursorRange { + primary: CCursor::new(start.clone()), + secondary: CCursor::new(max(start.clone(), extended)), + }); + output.state.set_ccursor_range(cursor.clone()); + output.state.store(ui.ctx(), output.response.id); + *last_cursor = cursor.clone(); + } + } - //text_edit_output.expect("TextEditOutput should exist at this point") - } + if previous_text != text.clone() { + *saved = false; + } + //text_edit_output = Some(output); + }); + }); + }; + if self.vscroll { + let scroll_area = egui::ScrollArea::vertical() + .id_source(format!("{}_outer_scroll", self.id)) + .stick_to_bottom(self.stick_to_bottom) + .vertical_scroll_offset(vertical_offset.clone()) + .show(ui, code_editor); + *vertical_offset = scroll_area.state.offset.y.clone(); + } else { + code_editor(ui); + } - fn toggle_start_of_line(&self, cursor_range : CCursorRange, text : String, head : &str) -> (String, isize) { - let mut substring = self.get_selection_substring(text.clone(), cursor_range.clone()).clone(); - let mut new_text : String = "".into(); - let extend : isize; - - if substring[1].contains(head) { - extend = - self.delta_char(substring[1].clone(), head); - substring[1] = substring[1].replace(&format!("\n{}", head), &"\n".to_string()); - } else { - extend = self.delta_char(substring[1].clone(), head); - substring[1] = substring[1].replace(&"\n".to_string(), &format!("\n{}", head)); - } - new_text.push_str(&substring[0].clone()); - new_text.push_str(&substring[1].clone()); - new_text.push_str(&substring[2].clone()); + //text_edit_output.expect("TextEditOutput should exist at this point") + } - return (new_text, extend) - } + fn toggle_start_of_line( + &self, + cursor_range: CCursorRange, + text: String, + head: &str, + ) -> (String, isize) { + let mut substring = self + .get_selection_substring(text.clone(), cursor_range.clone()) + .clone(); + let mut new_text: String = "".into(); + let extend: isize; + if substring[1].contains(head) { + extend = -self.delta_char(substring[1].clone(), head); + substring[1] = substring[1].replace(&format!("\n{}", head), &"\n".to_string()); + } else { + extend = self.delta_char(substring[1].clone(), head); + substring[1] = substring[1].replace(&"\n".to_string(), &format!("\n{}", head)); + } + new_text.push_str(&substring[0].clone()); + new_text.push_str(&substring[1].clone()); + new_text.push_str(&substring[2].clone()); - fn add_start_of_line(&self, cursor_range : CCursorRange, text : String, head : &str) -> (String, isize) { - let mut substring = self.get_selection_substring(text.clone(), cursor_range.clone()).clone(); - let mut new_text : String = "".into(); + return (new_text, extend); + } - let extend : isize = self.delta_char(substring[1].clone(), head); - substring[1] = substring[1].replace(&"\n".to_string(), &format!("\n{}", head)); - - new_text.push_str(&substring[0].clone()); - new_text.push_str(&substring[1].clone()); - new_text.push_str(&substring[2].clone()); + fn add_start_of_line( + &self, + cursor_range: CCursorRange, + text: String, + head: &str, + ) -> (String, isize) { + let mut substring = self + .get_selection_substring(text.clone(), cursor_range.clone()) + .clone(); + let mut new_text: String = "".into(); - return (new_text, extend) - } + let extend: isize = self.delta_char(substring[1].clone(), head); + substring[1] = substring[1].replace(&"\n".to_string(), &format!("\n{}", head)); - fn remove_start_of_line(&self, cursor_range : CCursorRange, text : String, head : &str) -> (String, isize) { - let mut substring = self.get_selection_substring(text.clone(), cursor_range.clone()).clone(); - let mut new_text : String = "".into(); - - let extend : isize = - self.delta_char(substring[1].clone(), head); - substring[1] = substring[1].replace(&format!("\n{}", head), &"\n".to_string()); - - new_text.push_str(&substring[0].clone()); - new_text.push_str(&substring[1].clone()); - new_text.push_str(&substring[2].clone()); + new_text.push_str(&substring[0].clone()); + new_text.push_str(&substring[1].clone()); + new_text.push_str(&substring[2].clone()); - return (new_text, extend) - } + return (new_text, extend); + } - fn get_selection_substring(&self, text : String, cursor_range : CCursorRange) -> Vec { - let start = min(cursor_range.primary.index, cursor_range.secondary.index); - let end = max(cursor_range.primary.index, cursor_range.secondary.index); + fn remove_start_of_line( + &self, + cursor_range: CCursorRange, + text: String, + head: &str, + ) -> (String, isize) { + let mut substring = self + .get_selection_substring(text.clone(), cursor_range.clone()) + .clone(); + let mut new_text: String = "".into(); - let mut first_char = max(0, start - 1); + let extend: isize = -self.delta_char(substring[1].clone(), head); + substring[1] = substring[1].replace(&format!("\n{}", head), &"\n".to_string()); - while first_char > 0 && text.char_at(first_char) != '\n' { - first_char -= 1; - } + new_text.push_str(&substring[0].clone()); + new_text.push_str(&substring[1].clone()); + new_text.push_str(&substring[2].clone()); - let last_char = end; - - return vec![text.slice(..first_char).to_string(), text.slice(first_char..last_char).to_string(), text.slice(last_char..).to_string()]; - } + return (new_text, extend); + } - fn delta_char(&self, text : String, modifier: &str) -> isize { - (modifier.len() * text.match_indices(&"\n".to_string()).collect::>().len()) as isize - } + fn get_selection_substring(&self, text: String, cursor_range: CCursorRange) -> Vec { + let start = min(cursor_range.primary.index, cursor_range.secondary.index); + let end = max(cursor_range.primary.index, cursor_range.secondary.index); - fn new_line(&self, cursor_range : CCursorRange, text : String) -> (String, isize) { - let cursor = min(cursor_range.primary.index, cursor_range.secondary.index); - - if cursor < 2 { - return (text.clone().to_string(), 1 as isize); - } + let mut first_char = max(0, start - 1); - let mut last_line_break = cursor - 1; + while first_char > 0 && text.char_at(first_char) != '\n' { + first_char -= 1; + } - while last_line_break > 0 && text.char_at(last_line_break) != '\n' { - last_line_break -= 1; - } - let indent_depth = text.slice(last_line_break..cursor).match_indices(&"\t".to_string()).collect::>().len(); + let last_char = end; - let new_indent_depth = indent_depth.clone(); - - let mut new_text : String = text.clone().slice(..(cursor + 1)).to_string(); - new_text.push_str(&"\t".repeat(new_indent_depth.clone())); - new_text.push_str(text.clone().slice((cursor + 1)..)); + return vec![ + text.slice(..first_char).to_string(), + text.slice(first_char..last_char).to_string(), + text.slice(last_char..).to_string(), + ]; + } - return (new_text.clone().to_string(), (new_indent_depth + 1) as isize); - } + fn delta_char(&self, text: String, modifier: &str) -> isize { + (modifier.len() + * text + .match_indices(&"\n".to_string()) + .collect::>() + .len()) as isize + } + + fn new_line(&self, cursor_range: CCursorRange, text: String) -> (String, isize) { + let cursor = min(cursor_range.primary.index, cursor_range.secondary.index); + + if cursor < 2 { + return (text.clone().to_string(), 1 as isize); + } + + let mut last_line_break = cursor - 1; + + while last_line_break > 0 && text.char_at(last_line_break) != '\n' { + last_line_break -= 1; + } + let indent_depth = text + .slice(last_line_break..cursor) + .match_indices(&"\t".to_string()) + .collect::>() + .len(); + + let new_indent_depth = indent_depth.clone(); + + let mut new_text: String = text.clone().slice(..(cursor + 1)).to_string(); + new_text.push_str(&"\t".repeat(new_indent_depth.clone())); + new_text.push_str(text.clone().slice((cursor + 1)..)); + + return ( + new_text.clone().to_string(), + (new_indent_depth + 1) as isize, + ); + } } diff --git a/src/calcifer/code_editor/themes/fantasy.rs b/src/calcifer/code_editor/themes/fantasy.rs index 9e073cd..483ef29 100644 --- a/src/calcifer/code_editor/themes/fantasy.rs +++ b/src/calcifer/code_editor/themes/fantasy.rs @@ -12,7 +12,7 @@ impl ColorTheme { comments: "#656565", // dark_gray functions: "#ffad69", // light orange keywords: "#48b1a7", // mid green - literals: "#d2d2d3", // + literals: "#d2d2d3", // numerics: "#ff7b4f", // orange punctuation: "#989898", // gray strs: "#cbd5a1", // light_green @@ -20,7 +20,7 @@ impl ColorTheme { special: "#48b1a7", // mid green }; - pub const ASH: ColorTheme = ColorTheme { + pub const ASH: ColorTheme = ColorTheme { name: "Ash", dark: true, bg: "#101010", diff --git a/src/calcifer/code_editor/themes/mod.rs b/src/calcifer/code_editor/themes/mod.rs index aeee5db..a07acc5 100644 --- a/src/calcifer/code_editor/themes/mod.rs +++ b/src/calcifer/code_editor/themes/mod.rs @@ -1,9 +1,9 @@ #![allow(dead_code)] pub mod ayu; +pub mod fantasy; pub mod github; pub mod gruvbox; pub mod sonokai; -pub mod fantasy; use super::syntax::TokenType; @@ -19,8 +19,8 @@ pub const DEFAULT_THEMES: [ColorTheme; 7] = [ ColorTheme::GITHUB_DARK, ColorTheme::GRUVBOX, ColorTheme::SONOKAI, - ColorTheme::FIRE, - ColorTheme::ASH, + ColorTheme::FIRE, + ColorTheme::ASH, ]; fn color_from_hex(hex: &str) -> Option { diff --git a/src/calcifer/code_editor/themes/sonokai.rs b/src/calcifer/code_editor/themes/sonokai.rs index 911026e..646e904 100644 --- a/src/calcifer/code_editor/themes/sonokai.rs +++ b/src/calcifer/code_editor/themes/sonokai.rs @@ -1,22 +1,22 @@ use super::ColorTheme; impl ColorTheme { - /// Original Author: sainnhe - /// Modified by p4ymak - pub const SONOKAI: ColorTheme = ColorTheme { - name: "Sonokai", - dark: true, - bg: "#2c2e34", // bg0 - cursor: "#76cce0", // blue - selection: "#444852", // bg5 - comments: "#7f8490", // gray - functions: "#9ed072", // green - keywords: "#fc5d7c", // red - literals: "#e2e2e3", // foreground - numerics: "#b39df3", // purple - punctuation: "#7f8490", // gray - strs: "#e7c664", // yellow - types: "#399ee6", // blue - special: "#f39660", // orange - }; + /// Original Author: sainnhe + /// Modified by p4ymak + pub const SONOKAI: ColorTheme = ColorTheme { + name: "Sonokai", + dark: true, + bg: "#2c2e34", // bg0 + cursor: "#76cce0", // blue + selection: "#444852", // bg5 + comments: "#7f8490", // gray + functions: "#9ed072", // green + keywords: "#fc5d7c", // red + literals: "#e2e2e3", // foreground + numerics: "#b39df3", // purple + punctuation: "#7f8490", // gray + strs: "#e7c664", // yellow + types: "#399ee6", // blue + special: "#f39660", // orange + }; } diff --git a/src/main.rs b/src/main.rs index a4081d7..3e58d6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,236 +1,247 @@ -mod tools; mod calcifer; +mod tools; -use eframe::egui; use calcifer::code_editor::ColorTheme; -use std::{path::Path, sync::Arc, time, thread, ops::Range}; +use eframe::egui; use egui::FontFamily::Proportional; use egui::FontId; -use egui::TextStyle::{Small, Button, Body, Heading, Monospace}; +use egui::TextStyle::{Body, Button, Heading, Monospace, Small}; +use std::{ops::Range, path::Path, sync::Arc, thread, time}; use calcifer::code_editor::themes::DEFAULT_THEMES; #[cfg(debug_assertions)] mod build { - pub const SAVE_PATH : &str = "/home/penwing/Documents/.save/debug/calcifer_save.json"; - pub const TITLE: &str = " debug"; + pub const SAVE_PATH: &str = "/home/penwing/Documents/.save/debug/calcifer_save.json"; + pub const TITLE: &str = " debug"; } #[cfg(not(debug_assertions))] mod build { - pub const SAVE_PATH : &str = "/home/penwing/Documents/.save/calcifer_save.json"; - pub const TITLE: &str = ""; + pub const SAVE_PATH: &str = "/home/penwing/Documents/.save/calcifer_save.json"; + pub const TITLE: &str = ""; } use build::SAVE_PATH; use build::TITLE; -const TERMINAL_HEIGHT : f32 = 200.0; -const TERMINAL_RANGE : Range = 100.0..500.0; -const RED : egui::Color32 = egui::Color32::from_rgb(235, 108, 99); -const TIME_LABELS : [&str; 7] = ["input", "settings", "tree", "terminal", "tabs", "content", "windows"]; -const MAX_FPS : f32 = 30.0; -const PATH_ROOT : &str = "/home/penwing/Documents/"; -const DISPLAY_PATH_DEPTH : usize = 3; -const MAX_TABS : usize = 20; - +const TERMINAL_HEIGHT: f32 = 200.0; +const TERMINAL_RANGE: Range = 100.0..500.0; +const RED: egui::Color32 = egui::Color32::from_rgb(235, 108, 99); +const TIME_LABELS: [&str; 7] = [ + "input", "settings", "tree", "terminal", "tabs", "content", "windows", +]; +const MAX_FPS: f32 = 30.0; +const PATH_ROOT: &str = "/home/penwing/Documents/"; +const DISPLAY_PATH_DEPTH: usize = 3; +const MAX_TABS: usize = 20; fn main() -> Result<(), eframe::Error> { - let icon_data = tools::load_icon(); - let options = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default() - .with_inner_size([1200.0, 800.0]) - .with_icon(Arc::new(icon_data)), - ..Default::default() - }; - - let app_state: tools::AppState; - // Attempt to load previous state - if Path::new(SAVE_PATH).exists() { - app_state = tools::load_state(SAVE_PATH).expect("Failed to load the save"); - } else { - app_state = tools::AppState { - tabs: vec![], - theme: 0, - }; - } + let icon_data = tools::load_icon(); + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default() + .with_inner_size([1200.0, 800.0]) + .with_icon(Arc::new(icon_data)), + ..Default::default() + }; - eframe::run_native( - &format!("Calcifer{}{}", tools::version(), TITLE), - options, - Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state))), - ) + let app_state: tools::AppState; + // Attempt to load previous state + if Path::new(SAVE_PATH).exists() { + app_state = tools::load_state(SAVE_PATH).expect("Failed to load the save"); + } else { + app_state = tools::AppState { + tabs: vec![], + theme: 0, + }; + } + + eframe::run_native( + &format!("Calcifer{}{}", tools::version(), TITLE), + options, + Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state))), + ) } - struct Calcifer { - selected_tab : tools::TabNumber, - tabs: Vec, + selected_tab: tools::TabNumber, + tabs: Vec, - command: String, - command_history: Vec, + command: String, + command_history: Vec, - theme: ColorTheme, - font_size: f32, - - tree_visible: bool, - profiler_visible: bool, - terminal_visible: bool, - - close_tab_confirm: tools::confirm::ConfirmWindow, - tab_to_close: usize, - refresh_confirm: tools::confirm::ConfirmWindow, - exit_confirm: tools::confirm::ConfirmWindow, - - search_menu: tools::search::SearchWindow, - settings_menu: tools::settings::SettingsWindow, - shortcuts_menu: tools::shortcuts::ShortcutsWindow, - - time_watch: Vec, - next_frame: time::Instant, + theme: ColorTheme, + font_size: f32, + + tree_visible: bool, + profiler_visible: bool, + terminal_visible: bool, + + close_tab_confirm: tools::confirm::ConfirmWindow, + tab_to_close: usize, + refresh_confirm: tools::confirm::ConfirmWindow, + exit_confirm: tools::confirm::ConfirmWindow, + + search_menu: tools::search::SearchWindow, + settings_menu: tools::settings::SettingsWindow, + shortcuts_menu: tools::shortcuts::ShortcutsWindow, + + time_watch: Vec, + next_frame: time::Instant, } - impl Default for Calcifer { - fn default() -> Self { - Self { - selected_tab: tools::TabNumber::from_index(0), - tabs: vec![tools::Tab::default()], + fn default() -> Self { + Self { + selected_tab: tools::TabNumber::from_index(0), + tabs: vec![tools::Tab::default()], - command: String::new(), - command_history: Vec::new(), + command: String::new(), + command_history: Vec::new(), - theme: DEFAULT_THEMES[0], - font_size: 14.0, - - tree_visible: false, - profiler_visible: false, - terminal_visible: false, - - close_tab_confirm: tools::confirm::ConfirmWindow::new("You have some unsaved changes, Do you still want to close this document ?", "Confirm Close"), - tab_to_close: 0, - refresh_confirm: tools::confirm::ConfirmWindow::new("You have some unsaved changes, Do you still want to refresh this document ?", "Confirm Refresh"), - exit_confirm: tools::confirm::ConfirmWindow::new("", "Confirm Exit"), - - search_menu: tools::search::SearchWindow::default(), - settings_menu: tools::settings::SettingsWindow::new(DEFAULT_THEMES[0]), - shortcuts_menu: tools::shortcuts::ShortcutsWindow::new(), - - time_watch: vec![0.0; TIME_LABELS.len()], - next_frame: time::Instant::now(), - } - } + theme: DEFAULT_THEMES[0], + font_size: 14.0, + + tree_visible: false, + profiler_visible: false, + terminal_visible: false, + + close_tab_confirm: tools::confirm::ConfirmWindow::new( + "You have some unsaved changes, Do you still want to close this document ?", + "Confirm Close", + ), + tab_to_close: 0, + refresh_confirm: tools::confirm::ConfirmWindow::new( + "You have some unsaved changes, Do you still want to refresh this document ?", + "Confirm Refresh", + ), + exit_confirm: tools::confirm::ConfirmWindow::new("", "Confirm Exit"), + + search_menu: tools::search::SearchWindow::default(), + settings_menu: tools::settings::SettingsWindow::new(DEFAULT_THEMES[0]), + shortcuts_menu: tools::shortcuts::ShortcutsWindow::new(), + + time_watch: vec![0.0; TIME_LABELS.len()], + next_frame: time::Instant::now(), + } + } } - impl eframe::App for Calcifer { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - thread::sleep(time::Duration::from_secs_f32(((1.0/MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0))); - self.next_frame = time::Instant::now(); - - let mut watch = time::Instant::now(); - - let mut style = (*ctx.style()).clone(); - style.text_styles = [ - (Heading, FontId::new(self.font_size * 1.6, Proportional)), - (Body, FontId::new(self.font_size, Proportional)), - (Monospace, FontId::new(self.font_size, Proportional)), - (Button, FontId::new(self.font_size, Proportional)), - (Small, FontId::new(self.font_size, Proportional)), - ] - .into(); - ctx.set_style(style); - - if ctx.input( |i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl) && !self.refresh_confirm.visible { - if self.tabs[self.selected_tab.to_index()].saved { - self.tabs[self.selected_tab.to_index()].refresh(); - } else { - self.refresh_confirm.ask(); - } - } - - if ctx.input( |i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) { - self.handle_save_file(self.save_tab()); - } - - if ctx.input( |i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl && i.modifiers.shift) { - self.handle_save_file(self.save_tab_as()); - } - - if ctx.input( |i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.alt) { - self.move_through_tabs(false); - } - - if ctx.input( |i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) { - self.move_through_tabs(true); - } - - if ctx.input( |i| i.zoom_delta() > 1.0) { - self.font_size = (self.font_size * 1.1).min(30.0); - } - - if ctx.input( |i| i.zoom_delta() < 1.0) { - self.font_size = (self.font_size / 1.1).max(10.0); - } + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + thread::sleep(time::Duration::from_secs_f32( + ((1.0 / MAX_FPS) - self.next_frame.elapsed().as_secs_f32()).max(0.0), + )); + self.next_frame = time::Instant::now(); - if ctx.input( |i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) { - self.search_menu.visible = !self.search_menu.visible.clone(); - self.search_menu.initialized = !self.search_menu.visible.clone(); - } - - if ctx.input(|i| i.viewport().close_requested()) { - let mut unsaved_tabs : Vec = vec![]; - for (index, tab) in self.tabs.iter().enumerate() { - if !tab.saved { - unsaved_tabs.push(index); - } - } - if unsaved_tabs.len() > 0 { - let mut unsaved_tabs_names : String = "".to_string(); - for index in unsaved_tabs.iter() { - unsaved_tabs_names.push_str(&self.tabs[*index].get_name()); - } - egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose); - self.exit_confirm.prompt = format!("You have some unsaved changes :\n{}\nDo you still want to exit ?", unsaved_tabs_names); - self.exit_confirm.ask(); - } - } - - self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); - - self.draw_settings(ctx); - - self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); - - self.draw_tree_panel(ctx); - - self.time_watch[2] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); - - self.draw_bottom_tray(ctx); - self.draw_terminal_panel(ctx); - - self.time_watch[3] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); - - self.draw_tab_panel(ctx); - - self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); - - self.draw_content_panel(ctx); - - self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); - - self.draw_windows(ctx); - - self.time_watch[6] = watch.elapsed().as_micros() as f32 / 1000.0; - } - - fn on_exit(&mut self, _gl : std::option::Option<&eframe::glow::Context>) { - self.save_state(); - } + let mut watch = time::Instant::now(); + + let mut style = (*ctx.style()).clone(); + style.text_styles = [ + (Heading, FontId::new(self.font_size * 1.6, Proportional)), + (Body, FontId::new(self.font_size, Proportional)), + (Monospace, FontId::new(self.font_size, Proportional)), + (Button, FontId::new(self.font_size, Proportional)), + (Small, FontId::new(self.font_size, Proportional)), + ] + .into(); + ctx.set_style(style); + + if ctx.input(|i| i.key_pressed(egui::Key::R) && i.modifiers.ctrl) + && !self.refresh_confirm.visible + { + if self.tabs[self.selected_tab.to_index()].saved { + self.tabs[self.selected_tab.to_index()].refresh(); + } else { + self.refresh_confirm.ask(); + } + } + + if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl) { + self.handle_save_file(self.save_tab()); + } + + if ctx.input(|i| i.key_pressed(egui::Key::S) && i.modifiers.ctrl && i.modifiers.shift) { + self.handle_save_file(self.save_tab_as()); + } + + if ctx.input(|i| i.key_pressed(egui::Key::ArrowLeft) && i.modifiers.alt) { + self.move_through_tabs(false); + } + + if ctx.input(|i| i.key_pressed(egui::Key::ArrowRight) && i.modifiers.alt) { + self.move_through_tabs(true); + } + + if ctx.input(|i| i.zoom_delta() > 1.0) { + self.font_size = (self.font_size * 1.1).min(30.0); + } + + if ctx.input(|i| i.zoom_delta() < 1.0) { + self.font_size = (self.font_size / 1.1).max(10.0); + } + + if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) { + self.search_menu.visible = !self.search_menu.visible.clone(); + self.search_menu.initialized = !self.search_menu.visible.clone(); + } + + if ctx.input(|i| i.viewport().close_requested()) { + let mut unsaved_tabs: Vec = vec![]; + for (index, tab) in self.tabs.iter().enumerate() { + if !tab.saved { + unsaved_tabs.push(index); + } + } + if unsaved_tabs.len() > 0 { + let mut unsaved_tabs_names: String = "".to_string(); + for index in unsaved_tabs.iter() { + unsaved_tabs_names.push_str(&self.tabs[*index].get_name()); + } + egui::Context::send_viewport_cmd(ctx, egui::ViewportCommand::CancelClose); + self.exit_confirm.prompt = format!( + "You have some unsaved changes :\n{}\nDo you still want to exit ?", + unsaved_tabs_names + ); + self.exit_confirm.ask(); + } + } + + self.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); + + self.draw_settings(ctx); + + self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); + + self.draw_tree_panel(ctx); + + self.time_watch[2] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); + + self.draw_bottom_tray(ctx); + self.draw_terminal_panel(ctx); + + self.time_watch[3] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); + + self.draw_tab_panel(ctx); + + self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); + + self.draw_content_panel(ctx); + + self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); + + self.draw_windows(ctx); + + self.time_watch[6] = watch.elapsed().as_micros() as f32 / 1000.0; + } + + fn on_exit(&mut self, _gl: std::option::Option<&eframe::glow::Context>) { + self.save_state(); + } } diff --git a/src/tools/confirm.rs b/src/tools/confirm.rs index 61387b2..f84290f 100644 --- a/src/tools/confirm.rs +++ b/src/tools/confirm.rs @@ -1,58 +1,53 @@ use eframe::egui; - pub struct ConfirmWindow { - pub visible: bool, - pub proceed: bool, - pub prompt: String, - id: String, + pub visible: bool, + pub proceed: bool, + pub prompt: String, + id: String, } - impl ConfirmWindow { - pub fn new(prompt: &str, id: &str) -> Self { - Self { - visible: false, - proceed: false, - prompt: prompt.to_string(), - id: id.to_string(), - } - } - - - pub fn show(&mut self, ctx: &egui::Context) { - let mut visible = self.visible.clone(); - egui::Window::new(self.id.clone()) - .open(&mut visible) - .vscroll(true) - .hscroll(true) - .show(ctx, |ui| self.ui(ui)); - self.visible = self.visible.clone() && visible; - } - - - fn ui(&mut self, ui: &mut egui::Ui) { - ui.set_min_width(250.0); - ui.label(self.prompt.clone()); - ui.vertical_centered(|ui| { - if ui.add(egui::Button::new("Yes")).clicked() { - self.proceed = true; - } - - if ui.add(egui::Button::new("No")).clicked() { - self.visible = false; - } - }); - } - - pub fn ask(&mut self) { - self.visible = true; - self.proceed = false; - } - - - pub fn close(&mut self) { - self.visible = false; - self.proceed = false; - } -} \ No newline at end of file + pub fn new(prompt: &str, id: &str) -> Self { + Self { + visible: false, + proceed: false, + prompt: prompt.to_string(), + id: id.to_string(), + } + } + + pub fn show(&mut self, ctx: &egui::Context) { + let mut visible = self.visible.clone(); + egui::Window::new(self.id.clone()) + .open(&mut visible) + .vscroll(true) + .hscroll(true) + .show(ctx, |ui| self.ui(ui)); + self.visible = self.visible.clone() && visible; + } + + fn ui(&mut self, ui: &mut egui::Ui) { + ui.set_min_width(250.0); + ui.label(self.prompt.clone()); + ui.vertical_centered(|ui| { + if ui.add(egui::Button::new("Yes")).clicked() { + self.proceed = true; + } + + if ui.add(egui::Button::new("No")).clicked() { + self.visible = false; + } + }); + } + + pub fn ask(&mut self) { + self.visible = true; + self.proceed = false; + } + + pub fn close(&mut self) { + self.visible = false; + self.proceed = false; + } +} diff --git a/src/tools/mod.rs b/src/tools/mod.rs index a906a3c..b69fdfe 100644 --- a/src/tools/mod.rs +++ b/src/tools/mod.rs @@ -1,13 +1,16 @@ -use std::{cmp::Ordering, path::PathBuf, path::Path, fs, fs::read_to_string, io::Write, path::Component, ffi::OsStr, fs::OpenOptions}; use crate::calcifer::code_editor::Syntax; -use eframe::egui; -use serde::{Serialize, Deserialize}; use crate::DISPLAY_PATH_DEPTH; +use eframe::egui; +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, ffi::OsStr, fs, fs::read_to_string, fs::OpenOptions, io::Write, path::Component, + path::Path, path::PathBuf, +}; use toml::Value; //my tools; -pub mod search; pub mod confirm; +pub mod search; pub mod settings; pub mod shortcuts; @@ -17,122 +20,122 @@ pub use terminal::*; pub mod tabs; pub use tabs::*; - #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct AppState { - pub tabs: Vec, - pub theme: usize, + pub tabs: Vec, + pub theme: usize, } - pub fn save_state(state: &AppState, file_path: &str) -> Result<(), std::io::Error> { - let serialized_state = serde_json::to_string(state)?; - - if let Some(parent_dir) = Path::new(file_path).parent() { - fs::create_dir_all(parent_dir)?; - } + let serialized_state = serde_json::to_string(state)?; - let mut file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(file_path)?; + if let Some(parent_dir) = Path::new(file_path).parent() { + fs::create_dir_all(parent_dir)?; + } - file.write_all(serialized_state.as_bytes())?; - - println!("Saved state at {}", file_path); + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(file_path)?; - Ok(()) + file.write_all(serialized_state.as_bytes())?; + + println!("Saved state at {}", file_path); + + Ok(()) } - pub fn load_state(file_path: &str) -> Result { - let serialized_state = read_to_string(file_path)?; + let serialized_state = read_to_string(file_path)?; - Ok(serde_json::from_str(&serialized_state)?) + Ok(serde_json::from_str(&serialized_state)?) } - pub fn load_icon() -> egui::IconData { - let (icon_rgba, icon_width, icon_height) = { - let icon = include_bytes!("../../assets/icon.png"); - let image = image::load_from_memory(icon) - .expect("Failed to open icon path") - .into_rgba8(); - let (width, height) = image.dimensions(); - let rgba = image.into_raw(); - (rgba, width, height) - }; - - egui::IconData { - rgba: icon_rgba, - width: icon_width, - height: icon_height, - } + let (icon_rgba, icon_width, icon_height) = { + let icon = include_bytes!("../../assets/icon.png"); + let image = image::load_from_memory(icon) + .expect("Failed to open icon path") + .into_rgba8(); + let (width, height) = image.dimensions(); + let rgba = image.into_raw(); + (rgba, width, height) + }; + + egui::IconData { + rgba: icon_rgba, + width: icon_width, + height: icon_height, + } } - -pub fn to_syntax(language : &str) -> Syntax { - match language { - "py" => Syntax::python(), - "rs" => Syntax::rust(), - _ => Syntax::shell(), - } +pub fn to_syntax(language: &str) -> Syntax { + match language { + "py" => Syntax::python(), + "rs" => Syntax::rust(), + _ => Syntax::shell(), + } } - pub fn sort_directories_first(a: &std::fs::DirEntry, b: &std::fs::DirEntry) -> Ordering { - let a_is_dir = a.path().is_dir(); - let b_is_dir = b.path().is_dir(); + let a_is_dir = a.path().is_dir(); + let b_is_dir = b.path().is_dir(); - // Directories come first, then files - if a_is_dir && !b_is_dir { - Ordering::Less - } else if !a_is_dir && b_is_dir { - Ordering::Greater - } else { - // Both are either directories or files, sort alphabetically - a.path().cmp(&b.path()) - } + // Directories come first, then files + if a_is_dir && !b_is_dir { + Ordering::Less + } else if !a_is_dir && b_is_dir { + Ordering::Greater + } else { + // Both are either directories or files, sort alphabetically + a.path().cmp(&b.path()) + } } - pub fn format_path(path: &Path) -> String { - let components: Vec<&OsStr> = path - .components() - .rev() - .take(DISPLAY_PATH_DEPTH) - .filter_map(|component| match component { - Component::RootDir | Component::CurDir => None, - _ => Some(component.as_os_str()), - }) - .collect(); + let components: Vec<&OsStr> = path + .components() + .rev() + .take(DISPLAY_PATH_DEPTH) + .filter_map(|component| match component { + Component::RootDir | Component::CurDir => None, + _ => Some(component.as_os_str()), + }) + .collect(); - format!("{}>", components.iter().rev().map(|&c| c.to_string_lossy()).collect::>().join("/")) + format!( + "{}>", + components + .iter() + .rev() + .map(|&c| c.to_string_lossy()) + .collect::>() + .join("/") + ) } pub fn version() -> String { - // Read the contents of the Cargo.toml file - if !Path::new("Cargo.toml").exists() { - return "".to_string() - } - let toml_content = fs::read_to_string("Cargo.toml").expect("Failed to read Cargo.toml"); + // Read the contents of the Cargo.toml file + if !Path::new("Cargo.toml").exists() { + return "".to_string(); + } + let toml_content = fs::read_to_string("Cargo.toml").expect("Failed to read Cargo.toml"); - // Parse the TOML content - let toml: Value = toml::from_str(&toml_content).expect("Failed to parse TOML"); + // Parse the TOML content + let toml: Value = toml::from_str(&toml_content).expect("Failed to parse TOML"); - // Extract version information - if let Some(package) = toml.get("package") { - if let Some(version) = package.get("version") { - if let Some(version_string) = version.as_str() { - println!("Version: {}", version_string); - return format!(" v{}", version_string) - } - } - } - return "".to_string() + // Extract version information + if let Some(package) = toml.get("package") { + if let Some(version) = package.get("version") { + if let Some(version_string) = version.as_str() { + println!("Version: {}", version_string); + return format!(" v{}", version_string); + } + } + } + return "".to_string(); } - #[cfg(test)] mod tests; diff --git a/src/tools/search.rs b/src/tools/search.rs index d1a9d13..6523004 100644 --- a/src/tools/search.rs +++ b/src/tools/search.rs @@ -1,237 +1,254 @@ -use std::{cmp::min}; use eframe::egui; +use std::cmp::min; -use crate::RED; use crate::tools::{tabs::Tab, tabs::TabNumber}; - +use crate::RED; enum Action { - Next, - Previous, - Replace, - Update, - None, + Next, + Previous, + Replace, + Update, + None, } - #[derive(Clone)] pub struct Selection { - pub tab: TabNumber, - pub start: usize, - pub end: usize, + pub tab: TabNumber, + pub start: usize, + pub end: usize, } - impl Default for Selection { - fn default() -> Self { - Self { - tab: TabNumber::from_index(0), - start: 0, - end: 0, - } - } + fn default() -> Self { + Self { + tab: TabNumber::from_index(0), + start: 0, + end: 0, + } + } } - pub struct SearchWindow { - pub visible: bool, - - search_text: String, - searched_text: String, - replace_text: String, + pub visible: bool, - pub initialized: bool, + search_text: String, + searched_text: String, + replace_text: String, - across_documents: bool, + pub initialized: bool, - results: Vec, - current_result: usize, + across_documents: bool, - pub result_selected: bool, - - row_height: f32, + results: Vec, + current_result: usize, + + pub result_selected: bool, + + row_height: f32, } - impl Default for SearchWindow { - fn default() -> Self { - Self { - visible: false, - - search_text: "".into(), - searched_text: "".into(), - replace_text: "".into(), - - initialized: false, + fn default() -> Self { + Self { + visible: false, - across_documents: false, + search_text: "".into(), + searched_text: "".into(), + replace_text: "".into(), - results: vec![], - current_result: 0, + initialized: false, - result_selected: true, - - row_height: 0.0, - } - } + across_documents: false, + + results: vec![], + current_result: 0, + + result_selected: true, + + row_height: 0.0, + } + } } - impl SearchWindow { - pub fn show(&mut self, ctx: &egui::Context, tabs: &mut Vec, selected_tab: &mut TabNumber) { - let mut visible = self.visible.clone(); - egui::Window::new("Search") - .open(&mut visible) //I want it to be able to change its visibility (if user close manually) - .vscroll(true) - .hscroll(true) - .show(ctx, |ui| self.ui(ui, tabs, selected_tab)); //but I want to edit the rest of the parameters and maybe close automatically - self.visible = self.visible.clone() && visible; - } - - - fn ui(&mut self, ui: &mut egui::Ui, tabs: &mut Vec, selected_tab: &mut TabNumber) { - ui.set_min_width(250.0); - - let font_id = egui::TextStyle::Body.resolve(ui.style()); - self.row_height = ui.fonts(|f| f.row_height(&font_id)); //+ ui.spacing().item_spacing.y; + pub fn show(&mut self, ctx: &egui::Context, tabs: &mut Vec, selected_tab: &mut TabNumber) { + let mut visible = self.visible.clone(); + egui::Window::new("Search") + .open(&mut visible) //I want it to be able to change its visibility (if user close manually) + .vscroll(true) + .hscroll(true) + .show(ctx, |ui| self.ui(ui, tabs, selected_tab)); //but I want to edit the rest of the parameters and maybe close automatically + self.visible = self.visible.clone() && visible; + } - let mut action : Action = Action::None; + fn ui(&mut self, ui: &mut egui::Ui, tabs: &mut Vec, selected_tab: &mut TabNumber) { + ui.set_min_width(250.0); - ui.horizontal(|ui| { - let Self { search_text, .. } = self; - - let response = ui.add(egui::TextEdit::singleline(search_text).desired_width(120.0).lock_focus(true)); - if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { - action = Action::Update; - } - if !self.initialized { - response.request_focus(); - self.initialized = true; - } - - if ui.add(egui::Button::new("Update")).clicked() { - action = Action::Update; - } + let font_id = egui::TextStyle::Body.resolve(ui.style()); + self.row_height = ui.fonts(|f| f.row_height(&font_id)); //+ ui.spacing().item_spacing.y; - if ui.add(egui::Button::new("<")).clicked() { - action = Action::Previous; - } + let mut action: Action = Action::None; - if self.search_text == self.searched_text && self.search_text.len() > 0 && self.results.len() == 0 { - ui.colored_label(RED, " 0/0 "); - } else { - ui.label(format!(" {}/{} ", min(self.current_result + 1, self.results.len()), self.results.len())); - } + ui.horizontal(|ui| { + let Self { search_text, .. } = self; - if ui.add(egui::Button::new(">")).clicked() { - action = Action::Next; - } - }); + let response = ui.add( + egui::TextEdit::singleline(search_text) + .desired_width(120.0) + .lock_focus(true), + ); + if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { + action = Action::Update; + } + if !self.initialized { + response.request_focus(); + self.initialized = true; + } - let previous_bool_state = self.across_documents.clone(); - ui.checkbox(&mut self.across_documents, "Across documents"); - if previous_bool_state != self.across_documents { - self.searched_text = "".into(); - } + if ui.add(egui::Button::new("Update")).clicked() { + action = Action::Update; + } - egui::CollapsingHeader::new("Replace") - .default_open(false) - .show(ui, |ui| { - ui.horizontal(|ui| { - let Self { replace_text, .. } = self; - ui.add(egui::TextEdit::singleline(replace_text).desired_width(120.0).lock_focus(true)); - if ui.add(egui::Button::new("Replace")).clicked() { - action = Action::Replace; - } - }); - }); + if ui.add(egui::Button::new("<")).clicked() { + action = Action::Previous; + } - match action { - Action::Update => self.search(tabs, selected_tab), - Action::Next => self.find_result(tabs, selected_tab, 1), - Action::Previous => self.find_result(tabs, selected_tab, -1), - Action::Replace => self.replace(tabs, selected_tab), - Action::None => (), - } - } - - - pub fn get_cursor_start(&self) -> usize { - self.results[self.current_result].start.clone() - } + if self.search_text == self.searched_text + && self.search_text.len() > 0 + && self.results.len() == 0 + { + ui.colored_label(RED, " 0/0 "); + } else { + ui.label(format!( + " {}/{} ", + min(self.current_result + 1, self.results.len()), + self.results.len() + )); + } + if ui.add(egui::Button::new(">")).clicked() { + action = Action::Next; + } + }); - pub fn get_cursor_end(&self) -> usize { - self.results[self.current_result].end.clone() - } - + let previous_bool_state = self.across_documents.clone(); + ui.checkbox(&mut self.across_documents, "Across documents"); + if previous_bool_state != self.across_documents { + self.searched_text = "".into(); + } - fn search(&mut self, tabs: &mut Vec, selected_tab: &mut TabNumber) { - if self.search_text.len() == 0 { - return - } + egui::CollapsingHeader::new("Replace") + .default_open(false) + .show(ui, |ui| { + ui.horizontal(|ui| { + let Self { replace_text, .. } = self; + ui.add( + egui::TextEdit::singleline(replace_text) + .desired_width(120.0) + .lock_focus(true), + ); + if ui.add(egui::Button::new("Replace")).clicked() { + action = Action::Replace; + } + }); + }); - let mut search_results: Vec = vec![]; + match action { + Action::Update => self.search(tabs, selected_tab), + Action::Next => self.find_result(tabs, selected_tab, 1), + Action::Previous => self.find_result(tabs, selected_tab, -1), + Action::Replace => self.replace(tabs, selected_tab), + Action::None => (), + } + } - if self.across_documents { - for (index, tab) in tabs.iter().enumerate() { - search_results.extend(self.match_text(tab.code.clone(), TabNumber::from_index(index))); - } - } else { - search_results.extend(self.match_text(tabs[selected_tab.to_index()].code.clone(), selected_tab.clone())); - } - - self.searched_text = self.search_text.clone(); - self.results = search_results.clone(); - - self.current_result = 0; - if self.results.len() > 0 { - self.find_result(tabs, selected_tab, 0); - } - } + pub fn get_cursor_start(&self) -> usize { + self.results[self.current_result].start.clone() + } + pub fn get_cursor_end(&self) -> usize { + self.results[self.current_result].end.clone() + } - fn match_text(&self, tab_text: String, tab_number: TabNumber) -> Vec { - let matches = tab_text.match_indices(&self.search_text.clone()).map(|(i, _)| Selection { - tab : tab_number.clone(), - start: i, - end: i + self.search_text.len(), - }).collect(); + fn search(&mut self, tabs: &mut Vec, selected_tab: &mut TabNumber) { + if self.search_text.len() == 0 { + return; + } - matches - } + let mut search_results: Vec = vec![]; + if self.across_documents { + for (index, tab) in tabs.iter().enumerate() { + search_results + .extend(self.match_text(tab.code.clone(), TabNumber::from_index(index))); + } + } else { + search_results.extend(self.match_text( + tabs[selected_tab.to_index()].code.clone(), + selected_tab.clone(), + )); + } - fn find_result(&mut self, tabs: &mut Vec, selected_tab: &mut TabNumber, direction: i32) { - if self.searched_text != self.search_text { - self.search(tabs, &mut *selected_tab); - } else if self.results.len() > 0 { - self.current_result = (self.current_result as i32 + direction + self.results.len() as i32) as usize % self.results.len(); - self.result_selected = false; - *selected_tab = self.results[self.current_result].tab.clone(); - - let target = self.results[self.current_result].start; - let code = tabs[selected_tab.to_index()].code.clone(); - let (upstream, _downstream) = code.split_at(target); - let row = upstream.match_indices(&"\n".to_string()).collect::>().len(); - tabs[selected_tab.to_index()].scroll_offset = self.row_height * row.saturating_sub(5) as f32; - } - } + self.searched_text = self.search_text.clone(); + self.results = search_results.clone(); + self.current_result = 0; + if self.results.len() > 0 { + self.find_result(tabs, selected_tab, 0); + } + } - fn replace(&mut self, tabs: &mut Vec, selected_tab: &mut TabNumber) { - if self.searched_text != self.search_text { - self.search(tabs, &mut *selected_tab); - } - - let mut done : Vec = vec![]; - for element in &self.results { - if done.contains(&element.tab) { - continue; - } - tabs[element.tab.to_index()].code = tabs[element.tab.to_index()].code.replace(&self.search_text, &self.replace_text); - tabs[element.tab.to_index()].saved = false; - done.push(element.tab.clone()) - } - } -} \ No newline at end of file + fn match_text(&self, tab_text: String, tab_number: TabNumber) -> Vec { + let matches = tab_text + .match_indices(&self.search_text.clone()) + .map(|(i, _)| Selection { + tab: tab_number.clone(), + start: i, + end: i + self.search_text.len(), + }) + .collect(); + + matches + } + + fn find_result(&mut self, tabs: &mut Vec, selected_tab: &mut TabNumber, direction: i32) { + if self.searched_text != self.search_text { + self.search(tabs, &mut *selected_tab); + } else if self.results.len() > 0 { + self.current_result = + (self.current_result as i32 + direction + self.results.len() as i32) as usize + % self.results.len(); + self.result_selected = false; + *selected_tab = self.results[self.current_result].tab.clone(); + + let target = self.results[self.current_result].start; + let code = tabs[selected_tab.to_index()].code.clone(); + let (upstream, _downstream) = code.split_at(target); + let row = upstream + .match_indices(&"\n".to_string()) + .collect::>() + .len(); + tabs[selected_tab.to_index()].scroll_offset = + self.row_height * row.saturating_sub(5) as f32; + } + } + + fn replace(&mut self, tabs: &mut Vec, selected_tab: &mut TabNumber) { + if self.searched_text != self.search_text { + self.search(tabs, &mut *selected_tab); + } + + let mut done: Vec = vec![]; + for element in &self.results { + if done.contains(&element.tab) { + continue; + } + tabs[element.tab.to_index()].code = tabs[element.tab.to_index()] + .code + .replace(&self.search_text, &self.replace_text); + tabs[element.tab.to_index()].saved = false; + done.push(element.tab.clone()) + } + } +} diff --git a/src/tools/settings.rs b/src/tools/settings.rs index 1c690a7..5aa6cf3 100644 --- a/src/tools/settings.rs +++ b/src/tools/settings.rs @@ -1,54 +1,50 @@ -use eframe::egui; use crate::ColorTheme; use crate::DEFAULT_THEMES; - +use eframe::egui; pub struct SettingsWindow { - pub visible: bool, - pub updated: bool, - pub theme: ColorTheme, + pub visible: bool, + pub updated: bool, + pub theme: ColorTheme, } - impl SettingsWindow { - pub fn new(theme : ColorTheme) -> Self { - Self { - visible: false, - updated: false, - theme, - } - } - - - pub fn show(&mut self, ctx: &egui::Context) { - let mut visible = self.visible.clone(); - egui::Window::new("Settings") - .open(&mut visible) //I want it to be able to change its visibility (if user close manually) - .vscroll(true) - .hscroll(true) - .show(ctx, |ui| self.ui(ui)); //but I want to edit the rest of the parameters and maybe close automatically - self.visible = self.visible.clone() && visible; - } - - - fn ui(&mut self, ui: &mut egui::Ui) { - ui.set_min_width(250.0); - ui.horizontal(|ui| { - ui.label("Theme "); - - let previous_theme = self.theme.clone(); - egui::ComboBox::from_label("") - .selected_text(format!("{}", self.theme.name)) - .show_ui(ui, |ui| { - ui.style_mut().wrap = Some(false); - ui.set_min_width(60.0); - for theme in DEFAULT_THEMES { - ui.selectable_value(&mut self.theme, theme, theme.name); - } - }); - if self.theme != previous_theme { - self.updated = true; - } - }); - } -} \ No newline at end of file + pub fn new(theme: ColorTheme) -> Self { + Self { + visible: false, + updated: false, + theme, + } + } + + pub fn show(&mut self, ctx: &egui::Context) { + let mut visible = self.visible.clone(); + egui::Window::new("Settings") + .open(&mut visible) //I want it to be able to change its visibility (if user close manually) + .vscroll(true) + .hscroll(true) + .show(ctx, |ui| self.ui(ui)); //but I want to edit the rest of the parameters and maybe close automatically + self.visible = self.visible.clone() && visible; + } + + fn ui(&mut self, ui: &mut egui::Ui) { + ui.set_min_width(250.0); + ui.horizontal(|ui| { + ui.label("Theme "); + + let previous_theme = self.theme.clone(); + egui::ComboBox::from_label("") + .selected_text(format!("{}", self.theme.name)) + .show_ui(ui, |ui| { + ui.style_mut().wrap = Some(false); + ui.set_min_width(60.0); + for theme in DEFAULT_THEMES { + ui.selectable_value(&mut self.theme, theme, theme.name); + } + }); + if self.theme != previous_theme { + self.updated = true; + } + }); + } +} diff --git a/src/tools/shortcuts.rs b/src/tools/shortcuts.rs index 222b44f..319d60d 100644 --- a/src/tools/shortcuts.rs +++ b/src/tools/shortcuts.rs @@ -1,42 +1,36 @@ use eframe::egui; - pub struct ShortcutsWindow { - pub visible: bool, + pub visible: bool, } - impl ShortcutsWindow { - pub fn new() -> Self { - Self { - visible: false, - } - } - - - pub fn show(&mut self, ctx: &egui::Context) { - let mut visible = self.visible.clone(); - egui::Window::new("Shortcuts") - .open(&mut visible) - .vscroll(true) - .hscroll(true) - .show(ctx, |ui| self.ui(ui,)); - self.visible = self.visible.clone() && visible; - } - - - fn ui(&mut self, ui: &mut egui::Ui) { - ui.set_min_width(250.0); - ui.label("Ctrl+S : save file"); - ui.label("Ctrl+Shift+S : save file as"); - ui.label("Ctrl+R : reload file"); - ui.separator(); - ui.label("Ctrl+F : open search window"); - ui.separator(); - ui.label("Ctrl+Z : undo"); - ui.label("Ctrl+Y : redo"); - ui.label("Tab on selection : add indent of selection"); - ui.label("Shift+Tab on selection : remove indent of selection"); - ui.label("Ctrl+E : comment selection"); - } -} \ No newline at end of file + pub fn new() -> Self { + Self { visible: false } + } + + pub fn show(&mut self, ctx: &egui::Context) { + let mut visible = self.visible.clone(); + egui::Window::new("Shortcuts") + .open(&mut visible) + .vscroll(true) + .hscroll(true) + .show(ctx, |ui| self.ui(ui)); + self.visible = self.visible.clone() && visible; + } + + fn ui(&mut self, ui: &mut egui::Ui) { + ui.set_min_width(250.0); + ui.label("Ctrl+S : save file"); + ui.label("Ctrl+Shift+S : save file as"); + ui.label("Ctrl+R : reload file"); + ui.separator(); + ui.label("Ctrl+F : open search window"); + ui.separator(); + ui.label("Ctrl+Z : undo"); + ui.label("Ctrl+Y : redo"); + ui.label("Tab on selection : add indent of selection"); + ui.label("Shift+Tab on selection : remove indent of selection"); + ui.label("Ctrl+E : comment selection"); + } +} diff --git a/src/tools/tabs.rs b/src/tools/tabs.rs index cf12862..8210dca 100644 --- a/src/tools/tabs.rs +++ b/src/tools/tabs.rs @@ -1,74 +1,79 @@ -use std::{fs::read_to_string, path::PathBuf}; use eframe::egui::text_edit::CCursorRange; +use std::{fs::read_to_string, path::PathBuf}; use crate::MAX_TABS; #[derive(Debug, PartialEq, Eq, Clone)] pub enum TabNumber { - Open, - Number(u8), // Using a range for numeric values + Open, + Number(u8), // Using a range for numeric values } impl TabNumber { - pub fn from_index(n: usize) -> TabNumber { - match n { - 0..=MAX_TABS => TabNumber::Number(n as u8), - _ => TabNumber::Number(0), - } - } + pub fn from_index(n: usize) -> TabNumber { + match n { + 0..=MAX_TABS => TabNumber::Number(n as u8), + _ => TabNumber::Number(0), + } + } - pub fn to_index(&self) -> usize { - match self { - TabNumber::Number(n) => *n as usize, - _ => 0, - } - } + pub fn to_index(&self) -> usize { + match self { + TabNumber::Number(n) => *n as usize, + _ => 0, + } + } } - #[derive(Clone, PartialEq)] pub struct Tab { - pub path : PathBuf, - pub code : String, - pub language : String, - pub saved : bool, - pub scroll_offset : f32, - pub last_cursor : Option, + pub path: PathBuf, + pub code: String, + pub language: String, + pub saved: bool, + pub scroll_offset: f32, + pub last_cursor: Option, } impl Default for Tab { - fn default() -> Self { - Self { - path: "untitled".into(), - code: "// Hello there, Master".into(), - language: "rs".into(), - saved: false, - scroll_offset: 0.0, - last_cursor: None, - } - } + fn default() -> Self { + Self { + path: "untitled".into(), + code: "// Hello there, Master".into(), + language: "rs".into(), + saved: false, + scroll_offset: 0.0, + last_cursor: None, + } + } } - impl Tab { - pub fn new(path : PathBuf) -> Self { - Self { - path: path.clone().into(), - code: read_to_string(path.clone()).expect("Not able to read the file").replace(&" ".repeat(4), "\t"), - language: path.to_str().unwrap().split('.').last().unwrap().into(), - saved: true, - scroll_offset: 0.0, - last_cursor: None, - } - - } - pub fn get_name(&self) -> String { - self.path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string() - } - - pub fn refresh(&mut self) { - self.code = read_to_string(self.path.clone()).expect("Not able to read the file").replace(&" ".repeat(4), "\t"); - self.saved = true; - println!("refreshed {}", self.path.display()); - } -} \ No newline at end of file + pub fn new(path: PathBuf) -> Self { + Self { + path: path.clone().into(), + code: read_to_string(path.clone()) + .expect("Not able to read the file") + .replace(&" ".repeat(4), "\t"), + language: path.to_str().unwrap().split('.').last().unwrap().into(), + saved: true, + scroll_offset: 0.0, + last_cursor: None, + } + } + pub fn get_name(&self) -> String { + self.path + .file_name() + .expect("Could not get Tab Name") + .to_string_lossy() + .to_string() + } + + pub fn refresh(&mut self) { + self.code = read_to_string(self.path.clone()) + .expect("Not able to read the file") + .replace(&" ".repeat(4), "\t"); + self.saved = true; + println!("refreshed {}", self.path.display()); + } +} diff --git a/src/tools/terminal.rs b/src/tools/terminal.rs index 222ddc7..1d2c2ac 100644 --- a/src/tools/terminal.rs +++ b/src/tools/terminal.rs @@ -1,112 +1,114 @@ use crate::tools::format_path; +use nix::fcntl::fcntl; +use nix::fcntl::FcntlArg; +use nix::fcntl::OFlag; use std::io::BufRead; use std::io::BufReader; use std::io::Read; +use std::os::fd::AsRawFd; use std::process::Stdio; use std::{env, path::Path, process::Command}; -use std::os::fd::AsRawFd; -use nix::fcntl::OFlag; -use nix::fcntl::FcntlArg; -use nix::fcntl::fcntl; pub struct CommandEntry { - pub env: String, - pub command: String, - pub output: String, - pub error: String, - pub output_buffer: BufReader, - pub error_buffer: BufReader, + pub env: String, + pub command: String, + pub output: String, + pub error: String, + pub output_buffer: BufReader, + pub error_buffer: BufReader, } impl CommandEntry { - pub fn new(command: String) -> Self { - let (stdout_reader, stderr_reader) = execute(command.clone()); + pub fn new(command: String) -> Self { + let (stdout_reader, stderr_reader) = execute(command.clone()); - CommandEntry { - env: format_path(&env::current_dir().expect("Could not find Shell Environnment")), - command, - output: String::new(), - error: String::new(), - output_buffer: stdout_reader, - error_buffer: stderr_reader, - } - } + CommandEntry { + env: format_path(&env::current_dir().expect("Could not find Shell Environnment")), + command, + output: String::new(), + error: String::new(), + output_buffer: stdout_reader, + error_buffer: stderr_reader, + } + } - pub fn update(&mut self) { - for line in self.output_buffer.by_ref().lines() { - match line { - Ok(line) => self.output += &format!("{}\n", line), - Err(_) => return, - } - } + pub fn update(&mut self) { + for line in self.output_buffer.by_ref().lines() { + match line { + Ok(line) => self.output += &format!("{}\n", line), + Err(_) => return, + } + } - for line in self.error_buffer.by_ref().lines() { - match line { - Ok(line) => self.error += &format!("{}\n", line), - Err(_) => return, - } - } - } + for line in self.error_buffer.by_ref().lines() { + match line { + Ok(line) => self.error += &format!("{}\n", line), + Err(_) => return, + } + } + } } pub fn send_command(command: String) -> CommandEntry { - if command.len() < 2 { - return CommandEntry::new(command); - } + if command.len() < 2 { + return CommandEntry::new(command); + } - if &command[..2] != "cd" { - return CommandEntry::new(command); - } + if &command[..2] != "cd" { + return CommandEntry::new(command); + } - if command.len() < 4 { - let mut entry = CommandEntry::new("echo Invalid cd, should provide path >&2".to_string()); - entry.command = command; - return entry; - } + if command.len() < 4 { + let mut entry = CommandEntry::new("echo Invalid cd, should provide path >&2".to_string()); + entry.command = command; + return entry; + } - let path_append = command[3..].replace("~", "/home/penwing"); - let path = Path::new(&path_append); + let path_append = command[3..].replace("~", "/home/penwing"); + let path = Path::new(&path_append); - if format!("{}", path.display()) == "/" { - let mut entry = CommandEntry::new("echo Root access denied >&2".to_string()); - entry.command = command; - return entry; - } + if format!("{}", path.display()) == "/" { + let mut entry = CommandEntry::new("echo Root access denied >&2".to_string()); + entry.command = command; + return entry; + } - if env::set_current_dir(path).is_ok() { - let mut entry = CommandEntry::new(format!("echo Moved to : {}", path.display())); - entry.command = command; - return entry; - } else { - let mut entry = - CommandEntry::new(format!("echo Could not find path : {} >&2", path.display())); - entry.command = command; - return entry; - } + if env::set_current_dir(path).is_ok() { + let mut entry = CommandEntry::new(format!("echo Moved to : {}", path.display())); + entry.command = command; + return entry; + } else { + let mut entry = + CommandEntry::new(format!("echo Could not find path : {} >&2", path.display())); + entry.command = command; + return entry; + } } pub fn execute( - command: String, + command: String, ) -> ( - BufReader, - BufReader, + BufReader, + BufReader, ) { - let mut child = Command::new("sh") - .arg("-c") - .arg(command.clone()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .expect("failed to execute process"); + let mut child = Command::new("sh") + .arg("-c") + .arg(command.clone()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("failed to execute process"); - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - let stdout_fd = stdout.as_raw_fd(); - let stderr_fd = stderr.as_raw_fd(); + let stdout = child.stdout.take().unwrap(); + let stderr = child.stderr.take().unwrap(); - fcntl(stdout_fd, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("Failed to set non-blocking mode"); - fcntl(stderr_fd, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("Failed to set non-blocking mode"); + let stdout_fd = stdout.as_raw_fd(); + let stderr_fd = stderr.as_raw_fd(); - return (BufReader::new(stdout), BufReader::new(stderr)); + fcntl(stdout_fd, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) + .expect("Failed to set non-blocking mode"); + fcntl(stderr_fd, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)) + .expect("Failed to set non-blocking mode"); + + return (BufReader::new(stdout), BufReader::new(stderr)); } diff --git a/src/tools/tests.rs b/src/tools/tests.rs index ad4187b..181dc22 100644 --- a/src/tools/tests.rs +++ b/src/tools/tests.rs @@ -1,85 +1,84 @@ #[cfg(test)] - mod tests { - use crate::tools::*; + use crate::tools::*; - #[test] - fn test_tab_number_conversions() { - let tab_num = TabNumber::from_index(3); - assert_eq!(tab_num, TabNumber::Three); - assert_eq!(tab_num.to_index(), 3); - } + #[test] + fn test_tab_number_conversions() { + let tab_num = TabNumber::from_index(3); + assert_eq!(tab_num, TabNumber::Three); + assert_eq!(tab_num.to_index(), 3); + } - #[test] - fn test_default_tab() { - let default_tab = Tab::default(); - assert_eq!(default_tab.path, PathBuf::from("untitled")); - assert_eq!(default_tab.code, "// Hello there, Master"); - assert_eq!(default_tab.language, "rs"); - assert!(!default_tab.saved); - assert_eq!(default_tab.scroll_offset, 0.0); - assert_eq!(default_tab.last_cursor, None); - } + #[test] + fn test_default_tab() { + let default_tab = Tab::default(); + assert_eq!(default_tab.path, PathBuf::from("untitled")); + assert_eq!(default_tab.code, "// Hello there, Master"); + assert_eq!(default_tab.language, "rs"); + assert!(!default_tab.saved); + assert_eq!(default_tab.scroll_offset, 0.0); + assert_eq!(default_tab.last_cursor, None); + } - #[test] - fn test_get_tab_name() { - let tab = Tab { - path: PathBuf::from("/path/to/file.rs"), - code: String::from(""), - language: String::from("rs"), - saved: true, - scroll_offset: 0.0, - last_cursor: None, - }; - assert_eq!(tab.get_name(), "file.rs"); - } + #[test] + fn test_get_tab_name() { + let tab = Tab { + path: PathBuf::from("/path/to/file.rs"), + code: String::from(""), + language: String::from("rs"), + saved: true, + scroll_offset: 0.0, + last_cursor: None, + }; + assert_eq!(tab.get_name(), "file.rs"); + } - #[test] - fn test_default_command_entry() { - let default_entry = CommandEntry::default(); - assert_eq!( - default_entry.env, - env::current_dir() - .expect("Could not find Shell Environnment") - .file_name() - .expect("Could not get Shell Environnment Name") - .to_string_lossy() - .to_string() - ); - assert_eq!(default_entry.command, ""); - assert_eq!(default_entry.output, ""); - assert_eq!(default_entry.error, ""); - } + #[test] + fn test_default_command_entry() { + let default_entry = CommandEntry::default(); + assert_eq!( + default_entry.env, + env::current_dir() + .expect("Could not find Shell Environnment") + .file_name() + .expect("Could not get Shell Environnment Name") + .to_string_lossy() + .to_string() + ); + assert_eq!(default_entry.command, ""); + assert_eq!(default_entry.output, ""); + assert_eq!(default_entry.error, ""); + } - #[test] - fn test_save_and_load_state() { - let tabs = vec![ - PathBuf::from("/path/to/file1.rs"), - PathBuf::from("/path/to/file2.py"), - ]; - let theme = 42; - let original_state = AppState { tabs, theme }; + #[test] + fn test_save_and_load_state() { + let tabs = vec![ + PathBuf::from("/path/to/file1.rs"), + PathBuf::from("/path/to/file2.py"), + ]; + let theme = 42; + let original_state = AppState { tabs, theme }; - // Save state to a temporary file - let temp_file_path = "/tmp/test_state.json"; - save_state(&original_state, temp_file_path).expect("Failed to save state"); + // Save state to a temporary file + let temp_file_path = "/tmp/test_state.json"; + save_state(&original_state, temp_file_path).expect("Failed to save state"); - // Load state from the temporary file - let loaded_state = load_state(temp_file_path).expect("Failed to load state"); + // Load state from the temporary file + let loaded_state = load_state(temp_file_path).expect("Failed to load state"); - assert_eq!(original_state, loaded_state); - } + assert_eq!(original_state, loaded_state); + } - #[test] - fn test_run_command() { - let cmd = "echo hello".to_string(); - let entry = run_command(cmd); - assert_eq!(entry.command, "echo hello"); - assert_eq!(entry.output.trim(), "hello"); - assert_eq!(entry.error, ""); - } + #[test] + fn test_run_command() { + let cmd = "echo hello".to_string(); + let entry = run_command(cmd); + assert_eq!(entry.command, "echo hello"); + assert_eq!(entry.output.trim(), "hello"); + assert_eq!(entry.error, ""); + } - // Add more tests as needed for other functions -} \ No newline at end of file + // Add more tests as needed for other functions +}