From 8436619c0e4bd2db0693939a4643c109c30a8b61 Mon Sep 17 00:00:00 2001 From: Penwing Date: Sat, 27 Jan 2024 12:34:55 +0100 Subject: [PATCH] chronological terminal result (error and output interlaced) --- README.md | 3 +- src/calcifer.rs | 512 +++++++++++++++++++-------------------- src/calcifer/app_base.rs | 390 ++++++++++++++--------------- src/main.rs | 336 ++++++++++++------------- src/tools/tabs.rs | 136 +++++------ src/tools/terminal.rs | 67 +++-- 6 files changed, 737 insertions(+), 707 deletions(-) diff --git a/README.md b/README.md index 1723a85..e023868 100644 --- a/README.md +++ b/README.md @@ -44,4 +44,5 @@ Added confirm prompt if unsaved Async terminal ! Real Ui -# 1.1.0 : \ No newline at end of file +# 1.1.0 : +Better error handling \ No newline at end of file diff --git a/src/calcifer.rs b/src/calcifer.rs index c8de908..888b558 100644 --- a/src/calcifer.rs +++ b/src/calcifer.rs @@ -15,277 +15,277 @@ 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, "⚡"); - - if self.tabs[self.selected_tab.to_index()].language == PROJECT_EXTENSION { - ui.separator(); - self.project_mode = self.toggle(ui, self.project_mode, "🛠"); - } - }); - }); - } + 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(); - }); - } + if self.tabs[self.selected_tab.to_index()].language == PROJECT_EXTENSION { + ui.separator(); + self.project_mode = self.toggle(ui, self.project_mode, "🛠"); + } + }); + }); + } - 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_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_terminal_panel(&mut self, ctx: &egui::Context) { - if !self.terminal_visible { - return; - } - egui::TopBottomPanel::bottom("terminal") - .default_height(super::TERMINAL_HEIGHT) - .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 = hex_str_to_color(self.theme.functions); - let entry_color = hex_str_to_color(self.theme.literals); - let bg_color = hex_str_to_color(self.theme.bg); + 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()); + }); + } - ui.label(""); + pub fn draw_terminal_panel(&mut self, ctx: &egui::Context) { + if !self.terminal_visible { + return; + } + egui::TopBottomPanel::bottom("terminal") + .default_height(super::TERMINAL_HEIGHT) + .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 = hex_str_to_color(self.theme.functions); + let entry_color = hex_str_to_color(self.theme.literals); + let bg_color = hex_str_to_color(self.theme.bg); - 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, - tools::format_path( - &env::current_dir().unwrap_or_else(|_| PathBuf::from("/")), - ), - ); - let response = ui.add( - egui::TextEdit::singleline(command) - .desired_width(f32::INFINITY) - .lock_focus(true), - ); + ui.label(""); - 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.is_empty() { - ui.colored_label(entry_color, &entry.output); - ui.end_row(); - } - if !entry.error.is_empty() { - ui.colored_label(super::RED, &entry.error); - ui.end_row(); - } - } - }); - }); - }); - }); - }); - } + 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, + tools::format_path( + &env::current_dir().unwrap_or_else(|_| PathBuf::from("/")), + ), + ); + let response = ui.add( + egui::TextEdit::singleline(command) + .desired_width(f32::INFINITY) + .lock_focus(true), + ); - 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 = - hex_str_to_color(self.theme.functions); - ui.style_mut().visuals.hyperlink_color = hex_str_to_color(self.theme.functions); - 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(hex_str_to_color(self.theme.bg)); - } - ui.selectable_value( - &mut self.selected_tab, - tools::TabNumber::from_index(index), - title, - ); + 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(); + for line in &entry.result { + let color = + if line.error { super::RED } else { entry_color }; + ui.colored_label(color, &line.text); + ui.end_row(); + } + } + }); + }); + }); + }); + }); + } - ui.style_mut().visuals.override_text_color = None; + 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 = + hex_str_to_color(self.theme.functions); + ui.style_mut().visuals.hyperlink_color = hex_str_to_color(self.theme.functions); + 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(hex_str_to_color(self.theme.bg)); + } + ui.selectable_value( + &mut self.selected_tab, + tools::TabNumber::from_index(index), + title, + ); - 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); - } - }); - }); - } + ui.style_mut().visuals.override_text_color = 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())); - } + 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); + } + }); + }); + } - ui.label("Picked file:"); - ui.monospace( - self.tabs[self.selected_tab.to_index()] - .path - .to_string_lossy() - .to_string(), - ); - }); + 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.separator(); - if self.project_mode && self.tabs[self.selected_tab.to_index()].language == PROJECT_EXTENSION { - self.draw_project_file(ui); - } else { - self.draw_code_file(ui); - } - }); - } + ui.label("Picked file:"); + ui.monospace( + self.tabs[self.selected_tab.to_index()] + .path + .to_string_lossy() + .to_string(), + ); + }); - 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; + ui.separator(); + if self.project_mode + && self.tabs[self.selected_tab.to_index()].language == PROJECT_EXTENSION + { + self.draw_project_file(ui); + } else { + self.draw_code_file(ui); + } + }); + } - 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; - } + 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; - 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, - ); - } - - fn draw_project_file(&mut self, ui: &mut egui::Ui) { - ui.label("project mode"); - } + 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; + } - 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; - } + 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, + ); + } - self.handle_confirm(); - } + fn draw_project_file(&mut self, ui: &mut egui::Ui) { + ui.label("project mode"); + } + + 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; + } + + self.handle_confirm(); + } } diff --git a/src/calcifer/app_base.rs b/src/calcifer/app_base.rs index 8e055f6..9612e20 100644 --- a/src/calcifer/app_base.rs +++ b/src/calcifer/app_base.rs @@ -12,226 +12,226 @@ use crate::TIME_LABELS; use tools::hex_str_to_color; 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); - } + 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(); - } - } + 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() - .map_or(true, |name| name.to_string_lossy() == "untitled") - { - 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; - } - Some(self.tabs[self.selected_tab.to_index()].path.clone()) - } - } + pub fn save_tab(&self) -> Option { + if self.tabs[self.selected_tab.to_index()] + .path + .file_name() + .map_or(true, |name| name.to_string_lossy() == "untitled") + { + 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; + } + 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); - } - None - } + 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); + } + 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 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() - }; + 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() - .map_or(true, |name| name.to_string_lossy() == "untitled") - { - new.open_file(Some(&path)); - } - } + for path in app_state.tabs { + if !path + .file_name() + .map_or(true, |name| name.to_string_lossy() == "untitled") + { + new.open_file(Some(&path)); + } + } - if new.tabs == vec![] { - new.open_file(None); - } + if new.tabs == vec![] { + new.open_file(None); + } - new - } + 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; - } + 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![]; + 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, - }; + 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); - } + 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 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 path.file_name().is_none() { - return Ok(()); - } + pub fn list_files(&mut self, ui: &mut egui::Ui, path: &Path) -> io::Result<()> { + if path.file_name().is_none() { + return Ok(()); + } - let name = path - .file_name() - .unwrap_or_else(|| OsStr::new("")) - .to_string_lossy() - .into_owned(); + let name = path + .file_name() + .unwrap_or_else(|| OsStr::new("")) + .to_string_lossy() + .into_owned(); - if !path.is_dir() { - if ui.button(name).clicked() { - self.open_file(Some(path)); - } - return Ok(()); - } + if !path.is_dir() { + if ui.button(name).clicked() { + self.open_file(Some(path)); + } + return Ok(()); + } - egui::CollapsingHeader::new(name).show(ui, |ui| match fs::read_dir(path) { - Err(err) => { - ui.label(format!("Error reading directory: {}", err)); - } - Ok(entries) => { - let mut paths: Vec> = entries - .map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e))) - .collect(); + egui::CollapsingHeader::new(name).show(ui, |ui| match fs::read_dir(path) { + Err(err) => { + ui.label(format!("Error reading directory: {}", err)); + } + Ok(entries) => { + let mut paths: Vec> = entries + .map(|r| r.map_err(|e| io::Error::new(io::ErrorKind::Other, e))) + .collect(); - paths.sort_by(|a, b| match (a, b) { - (Ok(entry_a), Ok(entry_b)) => tools::sort_directories_first(entry_a, entry_b), - (Err(_), Ok(_)) => std::cmp::Ordering::Greater, - (Ok(_), Err(_)) => std::cmp::Ordering::Less, - (Err(_), Err(_)) => std::cmp::Ordering::Equal, - }); + paths.sort_by(|a, b| match (a, b) { + (Ok(entry_a), Ok(entry_b)) => tools::sort_directories_first(entry_a, entry_b), + (Err(_), Ok(_)) => std::cmp::Ordering::Greater, + (Ok(_), Err(_)) => std::cmp::Ordering::Less, + (Err(_), Err(_)) => std::cmp::Ordering::Equal, + }); - for result in paths { - match result { - Ok(entry) => { - let _ = self.list_files(ui, &entry.path()); - } - Err(err) => { - ui.label(format!("Error processing directory entry: {}", err)); - } - } - } - } - }); - Ok(()) - } + for result in paths { + match result { + Ok(entry) => { + let _ = self.list_files(ui, &entry.path()); + } + Err(err) => { + ui.label(format!("Error processing directory entry: {}", err)); + } + } + } + } + }); + Ok(()) + } - pub fn open_file(&mut self, path_option: Option<&Path>) { - if let Some(path) = path_option.clone() { - for (index, tab) in self.tabs.clone().iter().enumerate() { - if tab.path == path { - self.selected_tab = tools::TabNumber::from_index(index); - return - } - } - } - 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 open_file(&mut self, path_option: Option<&Path>) { + if let Some(path) = path_option.clone() { + for (index, tab) in self.tabs.clone().iter().enumerate() { + if tab.path == path { + self.selected_tab = tools::TabNumber::from_index(index); + return; + } + } + } + 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 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; + pub fn toggle(&self, ui: &mut egui::Ui, display: bool, title: &str) -> bool { + let bg_color: Color32; + let text_color: Color32; - if display { - bg_color = hex_str_to_color(self.theme.functions); - text_color = hex_str_to_color(self.theme.bg); - } else { - bg_color = hex_str_to_color(self.theme.bg); - text_color = hex_str_to_color(self.theme.literals); - }; + if display { + bg_color = hex_str_to_color(self.theme.functions); + text_color = hex_str_to_color(self.theme.bg); + } else { + bg_color = hex_str_to_color(self.theme.bg); + text_color = hex_str_to_color(self.theme.literals); + }; - ui.style_mut().visuals.override_text_color = Some(text_color); + 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; - - display - } + if ui.add(egui::Button::new(title).fill(bg_color)).clicked() { + return !display; + } + ui.style_mut().visuals.override_text_color = None; - 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()) - .map(|(s, v)| format!("{} : {:.1} ms", s, v)) - .collect(); + display + } - let mut result = combined_string.join(" ; "); - result.push_str(&format!( - " total : {:.1} ms", - self.time_watch.clone().iter().sum::() - )); - result - } + 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()) + .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::() + )); + result + } } diff --git a/src/main.rs b/src/main.rs index f4ba34d..0a11241 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,25 +12,25 @@ 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 PROJECT_EXTENSION : &str = "project"; +const PROJECT_EXTENSION: &str = "project"; 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", + "input", "settings", "tree", "terminal", "tabs", "content", "windows", ]; const MAX_FPS: f32 = 30.0; const PATH_ROOT: &str = "/home/penwing/Documents/"; @@ -38,215 +38,215 @@ const DISPLAY_PATH_DEPTH: usize = 3; const MAX_TABS: usize = 20; fn main() -> Result<(), eframe::Error> { - let icon_data = tools::load_icon().unwrap_or_default(); + let icon_data = tools::load_icon().unwrap_or_default(); - let options = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default() - .with_inner_size([1200.0, 800.0]) - .with_icon(Arc::new(icon_data)), - ..Default::default() - }; + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default() + .with_inner_size([1200.0, 800.0]) + .with_icon(Arc::new(icon_data)), + ..Default::default() + }; - // Attempt to load previous state - let app_state: tools::AppState = if Path::new(SAVE_PATH).exists() { - match tools::load_state(SAVE_PATH) { - Ok(app_state) => app_state, - Err(_) => tools::AppState::default(), - } - } else { - tools::AppState::default() - }; + // Attempt to load previous state + let app_state: tools::AppState = if Path::new(SAVE_PATH).exists() { + match tools::load_state(SAVE_PATH) { + Ok(app_state) => app_state, + Err(_) => tools::AppState::default(), + } + } else { + tools::AppState::default() + }; - eframe::run_native( - &format!("Calcifer{}", TITLE), - options, - Box::new(move |_cc| Box::from(Calcifer::from_app_state(app_state))), - ) + eframe::run_native( + &format!("Calcifer{}", 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, - - project_mode: bool, + theme: ColorTheme, + font_size: f32, - tree_visible: bool, - profiler_visible: bool, - terminal_visible: bool, + project_mode: bool, - close_tab_confirm: tools::confirm::ConfirmWindow, - tab_to_close: usize, - refresh_confirm: tools::confirm::ConfirmWindow, - exit_confirm: tools::confirm::ConfirmWindow, + tree_visible: bool, + profiler_visible: bool, + terminal_visible: bool, - search_menu: tools::search::SearchWindow, - settings_menu: tools::settings::SettingsWindow, - shortcuts_menu: tools::shortcuts::ShortcutsWindow, + close_tab_confirm: tools::confirm::ConfirmWindow, + tab_to_close: usize, + refresh_confirm: tools::confirm::ConfirmWindow, + exit_confirm: tools::confirm::ConfirmWindow, - time_watch: Vec, - next_frame: time::Instant, + 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, - - project_mode: true, + theme: DEFAULT_THEMES[0], + font_size: 14.0, - tree_visible: false, - profiler_visible: false, - terminal_visible: false, + project_mode: true, - 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"), + tree_visible: false, + profiler_visible: false, + terminal_visible: false, - search_menu: tools::search::SearchWindow::default(), - settings_menu: tools::settings::SettingsWindow::new(DEFAULT_THEMES[0]), - shortcuts_menu: tools::shortcuts::ShortcutsWindow::new(), + 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"), - time_watch: vec![0.0; TIME_LABELS.len()], - next_frame: time::Instant::now(), - } - } + 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(); + 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 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); + 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::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) { + 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::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::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.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).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.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; - self.search_menu.initialized = !self.search_menu.visible; - } + if ctx.input(|i| i.key_pressed(egui::Key::F) && i.modifiers.ctrl) { + self.search_menu.visible = !self.search_menu.visible; + self.search_menu.initialized = !self.search_menu.visible; + } - 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.is_empty() { - 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(); - } - } + 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.is_empty() { + 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.time_watch[0] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); - self.draw_settings(ctx); + self.draw_settings(ctx); - self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); + self.time_watch[1] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); - self.draw_tree_panel(ctx); + self.draw_tree_panel(ctx); - self.time_watch[2] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); + 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.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.time_watch[3] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); - self.draw_tab_panel(ctx); + self.draw_tab_panel(ctx); - self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); + self.time_watch[4] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); - self.draw_content_panel(ctx); + self.draw_content_panel(ctx); - self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0; - watch = time::Instant::now(); + self.time_watch[5] = watch.elapsed().as_micros() as f32 / 1000.0; + watch = time::Instant::now(); - self.draw_windows(ctx); + self.draw_windows(ctx); - self.time_watch[6] = watch.elapsed().as_micros() as f32 / 1000.0; - } + 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(); - } + fn on_exit(&mut self, _gl: std::option::Option<&eframe::glow::Context>) { + self.save_state(); + } } diff --git a/src/tools/tabs.rs b/src/tools/tabs.rs index e0a6254..a8ab173 100644 --- a/src/tools/tabs.rs +++ b/src/tools/tabs.rs @@ -5,96 +5,96 @@ 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 { - let text = read_file_contents(&path).replace(&" ".repeat(4), "\t"); - let file_path = format_file_path(&path, &text); - let extension = file_path - .extension() - .and_then(|ext| ext.to_str()) - .unwrap_or_default(); + pub fn new(path: PathBuf) -> Self { + let text = read_file_contents(&path).replace(&" ".repeat(4), "\t"); + let file_path = format_file_path(&path, &text); + let extension = file_path + .extension() + .and_then(|ext| ext.to_str()) + .unwrap_or_default(); - Self { - path: file_path.clone(), - code: text, - language: extension.into(), - saved: true, - scroll_offset: 0.0, - last_cursor: None, - } - } + Self { + path: file_path.clone(), + code: text, + language: extension.into(), + saved: true, + scroll_offset: 0.0, + last_cursor: None, + } + } - pub fn get_name(&self) -> String { - self.path - .file_name() - .map_or("untitled".to_string(), |name| { - name.to_string_lossy().to_string() - }) - } + pub fn get_name(&self) -> String { + self.path + .file_name() + .map_or("untitled".to_string(), |name| { + name.to_string_lossy().to_string() + }) + } - pub fn refresh(&mut self) { - let text = read_file_contents(&self.path).replace(&" ".repeat(4), "\t"); - let file_path = format_file_path(&self.path, &text); + pub fn refresh(&mut self) { + let text = read_file_contents(&self.path).replace(&" ".repeat(4), "\t"); + let file_path = format_file_path(&self.path, &text); - self.code = text; - self.path = file_path; - self.saved = true; - } + self.code = text; + self.path = file_path; + self.saved = true; + } } fn read_file_contents(path: &Path) -> String { - read_to_string(path) - .map_err(|err| format!("// Error reading file: {}", err)) - .unwrap_or_else(|err_msg| err_msg) + read_to_string(path) + .map_err(|err| format!("// Error reading file: {}", err)) + .unwrap_or_else(|err_msg| err_msg) } fn format_file_path(path: &Path, contents: &str) -> PathBuf { - if contents.contains("Error reading file") { - "untitled".into() - } else { - path.to_path_buf() - } + if contents.contains("Error reading file") { + "untitled".into() + } else { + path.to_path_buf() + } } diff --git a/src/tools/terminal.rs b/src/tools/terminal.rs index 174a3be..1ee4760 100644 --- a/src/tools/terminal.rs +++ b/src/tools/terminal.rs @@ -4,7 +4,7 @@ use nix::fcntl::FcntlArg; use nix::fcntl::OFlag; use std::io::BufRead; use std::io::BufReader; -use std::io::Read; +//use std::io::Read; use std::os::fd::AsRawFd; use std::process::Stdio; use std::{env, path::Path, path::PathBuf, process::Command}; @@ -14,44 +14,62 @@ pub struct Buffer { pub error_buffer: BufReader, } +pub struct Line { + pub text: String, + pub error: bool, +} + +impl Line { + fn output(text: String) -> Self { + Self { + text: remove_line_break(text), + error: false, + } + } + fn error(text: String) -> Self { + Self { + text: remove_line_break(text), + error: true, + } + } +} + pub struct CommandEntry { pub env: String, pub command: String, - pub output: String, - pub error: String, + pub result: Vec, pub buffer: Option, } impl CommandEntry { pub fn new(env: String, command: String) -> Self { - let (buffer, error) = match execute(command.clone()) { - Ok(command_buffer) => (Some(command_buffer), String::new()), - Err(err) => (None, format!("failed to get results: {}", err)), + let (buffer, result) = match execute(command.clone()) { + Ok(command_buffer) => (Some(command_buffer), vec![]), + Err(err) => ( + None, + vec![Line::error(format!("failed to get results: {}", err))], + ), }; CommandEntry { env, command, - output: String::new(), - error, + result, buffer, } } pub fn update(&mut self) { if let Some(buffer) = &mut self.buffer { - for line in buffer.output_buffer.by_ref().lines() { - match line { - Ok(line) => self.output += &format!("{}\n", line), - Err(_) => return, - } + let mut output = String::new(); + let _ = buffer.output_buffer.read_line(&mut output); + if !remove_line_break(output.to_string()).is_empty() { + self.result.push(Line::output(format!("{}\n", output))); } - - for line in buffer.error_buffer.by_ref().lines() { - match line { - Ok(line) => self.error += &format!("{}\n", line), - Err(_) => return, - } + let mut error = String::new(); + let _ = buffer.error_buffer.read_line(&mut error); + if !remove_line_break(error.to_string()).is_empty() { + self.result.push(Line::error(format!("{}\n", error))); } } } @@ -129,3 +147,14 @@ pub fn execute(command: String) -> Result { error_buffer, }) } + +fn remove_line_break(input: String) -> String { + let mut text = input.clone(); + while text.ends_with('\n') { + text.pop(); + if text.ends_with('\r') { + text.pop(); + } + } + text +}