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