fixed linebreak bug
This commit is contained in:
parent
89f71ddc06
commit
b19905ce94
|
@ -24,7 +24,7 @@ Added indent recognition (when there is a line break, the indentation level is k
|
||||||
|
|
||||||
# 1.0.3 :
|
# 1.0.3 :
|
||||||
Added testing
|
Added testing
|
||||||
Added Ctrl+T : turn 4 spaces into tab across the whole document
|
Added Ctrl+T : refresh current tab
|
||||||
Added Time debug
|
Added Time debug
|
||||||
Added Tree toggle for performance
|
Added Tree toggle for performance
|
||||||
Added Alt+Arrows to move through tabs
|
Added Alt+Arrows to move through tabs
|
||||||
|
@ -32,3 +32,4 @@ Added Zoom
|
||||||
Added cd
|
Added cd
|
||||||
Added terminal color
|
Added terminal color
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"tabs":["/home/penwing/Documents/notes/victory2.txt","/home/penwing/Documents/projects/rust/calcifer/src/calcifer/code_editor/themes/sonokai.rs"],"theme":4}
|
{"tabs":["/home/penwing/Documents/notes/victory2.txt","/home/penwing/Documents/projects/rust/calcifer/src/calcifer/code_editor/themes/sonokai.rs"],"theme":6}
|
175
src/calcifer.rs
175
src/calcifer.rs
|
@ -1,17 +1,21 @@
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui::{text::CCursor, text_edit::CCursorRange};
|
use egui::{text::CCursor, text_edit::CCursorRange};
|
||||||
use std::{env, path::Path, path::PathBuf, cmp::max, io, fs, cmp::min};
|
use std::{env, path::Path, cmp::max};
|
||||||
|
|
||||||
use crate::tools;
|
use crate::tools;
|
||||||
|
use crate::Calcifer;
|
||||||
use crate::TIME_LABELS;
|
use crate::TIME_LABELS;
|
||||||
use crate::PATH_ROOT;
|
use crate::PATH_ROOT;
|
||||||
|
use crate::MAX_TABS;
|
||||||
|
|
||||||
pub mod code_editor;
|
pub mod code_editor;
|
||||||
use code_editor::CodeEditor;
|
use code_editor::CodeEditor;
|
||||||
use code_editor::themes::DEFAULT_THEMES;
|
use code_editor::themes::DEFAULT_THEMES;
|
||||||
|
|
||||||
|
mod app_base;
|
||||||
|
|
||||||
|
|
||||||
impl super::Calcifer {
|
impl Calcifer {
|
||||||
pub fn draw_settings(&mut self, ctx: &egui::Context) {
|
pub fn draw_settings(&mut self, ctx: &egui::Context) {
|
||||||
egui::TopBottomPanel::top("settings")
|
egui::TopBottomPanel::top("settings")
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
|
@ -19,7 +23,7 @@ impl super::Calcifer {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.add(egui::Button::new("open file")).clicked() {
|
if ui.add(egui::Button::new("open file")).clicked() {
|
||||||
if let Some(path) = rfd::FileDialog::new().set_directory(Path::new(&PATH_ROOT)).pick_file() {
|
if let Some(path) = rfd::FileDialog::new().set_directory(Path::new(&PATH_ROOT)).pick_file() {
|
||||||
self.selected_tab = self.open_file(&path);
|
self.open_file(Some(&path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
@ -53,6 +57,7 @@ impl super::Calcifer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn draw_tree_panel(&mut self, ctx: &egui::Context) {
|
pub fn draw_tree_panel(&mut self, ctx: &egui::Context) {
|
||||||
if !self.tree_display {
|
if !self.tree_display {
|
||||||
return
|
return
|
||||||
|
@ -65,6 +70,7 @@ impl super::Calcifer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn draw_terminal_panel(&mut self, ctx: &egui::Context) {
|
pub fn draw_terminal_panel(&mut self, ctx: &egui::Context) {
|
||||||
egui::TopBottomPanel::bottom("terminal")
|
egui::TopBottomPanel::bottom("terminal")
|
||||||
.default_height(super::TERMINAL_HEIGHT.clone())
|
.default_height(super::TERMINAL_HEIGHT.clone())
|
||||||
|
@ -113,6 +119,7 @@ impl super::Calcifer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn draw_tab_panel(&mut self, ctx: &egui::Context) {
|
pub fn draw_tab_panel(&mut self, ctx: &egui::Context) {
|
||||||
egui::TopBottomPanel::top("tabs")
|
egui::TopBottomPanel::top("tabs")
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
|
@ -137,16 +144,17 @@ impl super::Calcifer {
|
||||||
}
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
}
|
}
|
||||||
if tools::TabNumber::from_index(self.tabs.len()) != tools::TabNumber::None {
|
if self.tabs.len() < MAX_TABS {
|
||||||
ui.selectable_value(&mut self.selected_tab, tools::TabNumber::Open, "+");
|
ui.selectable_value(&mut self.selected_tab, tools::TabNumber::Open, "+");
|
||||||
}
|
}
|
||||||
if self.selected_tab == tools::TabNumber::Open {
|
if self.selected_tab == tools::TabNumber::Open {
|
||||||
self.selected_tab = self.new_tab();
|
self.open_file(None);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn draw_content_panel(&mut self, ctx: &egui::Context) {
|
pub fn draw_content_panel(&mut self, ctx: &egui::Context) {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
@ -159,16 +167,14 @@ impl super::Calcifer {
|
||||||
ui.label("Picked file:");
|
ui.label("Picked file:");
|
||||||
ui.monospace(self.tabs[self.selected_tab.to_index()].path.to_string_lossy().to_string());
|
ui.monospace(self.tabs[self.selected_tab.to_index()].path.to_string_lossy().to_string());
|
||||||
});
|
});
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
if self.selected_tab == tools::TabNumber::None {
|
ui.separator();
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.draw_code_file(ui);
|
self.draw_code_file(ui);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn draw_code_file(&mut self, ui: &mut egui::Ui) {
|
fn draw_code_file(&mut self, ui: &mut egui::Ui) {
|
||||||
let current_tab = &mut self.tabs[self.selected_tab.to_index()];
|
let current_tab = &mut self.tabs[self.selected_tab.to_index()];
|
||||||
let lines = current_tab.code.chars().filter(|&c| c == '\n').count() + 1;
|
let lines = current_tab.code.chars().filter(|&c| c == '\n').count() + 1;
|
||||||
|
@ -190,155 +196,4 @@ impl super::Calcifer {
|
||||||
.with_numlines(true)
|
.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);
|
.show(ui, &mut current_tab.code, &mut current_tab.saved, &mut current_tab.last_cursor, &mut current_tab.scroll_offset, override_cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
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" {
|
|
||||||
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().save_file() {
|
|
||||||
if let Err(err) = fs::write(&path, &self.tabs[self.selected_tab.to_index()].code) {
|
|
||||||
eprintln!("Error writing file: {}", err);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
return Some(path);
|
|
||||||
}
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_save_file(&mut self, path_option : Option<PathBuf>) {
|
|
||||||
if let Some(path) = path_option {
|
|
||||||
println!("File saved successfully at: {:?}", path);
|
|
||||||
self.tabs[self.selected_tab.to_index()].path = path;
|
|
||||||
self.tabs[self.selected_tab.to_index()].saved = true;
|
|
||||||
} else {
|
|
||||||
println!("File save failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_app_state(app_state: tools::AppState) -> Self {
|
|
||||||
let mut new = Self {
|
|
||||||
theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)],
|
|
||||||
tabs: Vec::new(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
for path in app_state.tabs {
|
|
||||||
if path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string() != "untitled" {
|
|
||||||
new.open_file(&path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if new.tabs == vec![] {
|
|
||||||
new.new_tab();
|
|
||||||
}
|
|
||||||
|
|
||||||
new
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_state(&self) {
|
|
||||||
let mut state_theme : usize = 0;
|
|
||||||
if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) {
|
|
||||||
state_theme = theme;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut state_tabs = vec![];
|
|
||||||
|
|
||||||
for tab in &self.tabs {
|
|
||||||
state_tabs.push(tab.path.clone());
|
|
||||||
}
|
|
||||||
let app_state = tools::AppState {
|
|
||||||
tabs: state_tabs,
|
|
||||||
theme: state_theme,
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = tools::save_state(&app_state, super::SAVE_PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn indent_with_tabs(&mut self) {
|
|
||||||
let current_tab = &mut self.tabs[self.selected_tab.to_index()];
|
|
||||||
current_tab.code = current_tab.code.replace(" ", "\t")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_through_tabs(&mut self, forward : bool) {
|
|
||||||
let new_index = if forward {
|
|
||||||
(self.selected_tab.to_index() + 1) % self.tabs.len()
|
|
||||||
} else {
|
|
||||||
self.selected_tab.to_index().checked_sub(1).unwrap_or(self.tabs.len() - 1)
|
|
||||||
};
|
|
||||||
self.selected_tab = tools::TabNumber::from_index(new_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Sort the vector using the custom sorting function
|
|
||||||
paths.sort_by(|a, b| tools::sort_directories_first(a, b));
|
|
||||||
|
|
||||||
for result in paths {
|
|
||||||
//let result = path_result.expect("Failed to get path");
|
|
||||||
//let full_path = result.path();
|
|
||||||
let _ = self.list_files(ui, &result.path());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
//ui.label(name.to_string_lossy());
|
|
||||||
if ui.button(name.to_string_lossy()).clicked() {
|
|
||||||
self.selected_tab = self.open_file(&path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_file(&mut self, path: &Path) -> tools::TabNumber {
|
|
||||||
if tools::TabNumber::from_index(self.tabs.len()) == tools::TabNumber::None {
|
|
||||||
return tools::TabNumber::None
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_tab = tools::Tab {
|
|
||||||
path: path.into(),
|
|
||||||
code: fs::read_to_string(path).expect("Not able to read the file").replace(" ", "\t"),
|
|
||||||
language: path.to_str().unwrap().split('.').last().unwrap().into(),
|
|
||||||
saved: true,
|
|
||||||
..tools::Tab::default()
|
|
||||||
};
|
|
||||||
self.tabs.push(new_tab);
|
|
||||||
|
|
||||||
return tools::TabNumber::from_index(self.tabs.len() - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_tab(&mut self) -> tools::TabNumber {
|
|
||||||
self.tabs.push(tools::Tab::default());
|
|
||||||
return tools::TabNumber::from_index(self.tabs.len() - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_tab(&mut self, index : usize) -> tools::TabNumber {
|
|
||||||
self.tabs.remove(index);
|
|
||||||
return tools::TabNumber::from_index(min(index, self.tabs.len() - 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle(&self, ui: &mut egui::Ui, display : bool, title : &str) -> bool {
|
|
||||||
let text = if display.clone() {
|
|
||||||
format!("hide {}", title)
|
|
||||||
} else {
|
|
||||||
format!("show {}", title)
|
|
||||||
};
|
|
||||||
|
|
||||||
if ui.add(egui::Button::new(text)).clicked() {
|
|
||||||
return !display
|
|
||||||
}
|
|
||||||
return display
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
152
src/calcifer/app_base.rs
Normal file
152
src/calcifer/app_base.rs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
use std::{path::PathBuf, fs, path::Path, cmp::min, io};
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
use crate::Calcifer;
|
||||||
|
use crate::tools;
|
||||||
|
use crate::PATH_ROOT;
|
||||||
|
use crate::DEFAULT_THEMES;
|
||||||
|
use crate::MAX_TABS;
|
||||||
|
use crate::SAVE_PATH;
|
||||||
|
|
||||||
|
|
||||||
|
impl Calcifer {
|
||||||
|
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" {
|
||||||
|
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 handle_save_file(&mut self, path_option : Option<PathBuf>) {
|
||||||
|
if let Some(path) = path_option {
|
||||||
|
println!("File saved successfully at: {:?}", path);
|
||||||
|
self.tabs[self.selected_tab.to_index()].path = path;
|
||||||
|
self.tabs[self.selected_tab.to_index()].saved = true;
|
||||||
|
} else {
|
||||||
|
println!("File save failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn from_app_state(app_state: tools::AppState) -> Self {
|
||||||
|
let mut new = Self {
|
||||||
|
theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)],
|
||||||
|
tabs: Vec::new(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
for path in app_state.tabs {
|
||||||
|
if path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string() != "untitled" {
|
||||||
|
new.open_file(Some(&path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if new.tabs == vec![] {
|
||||||
|
new.open_file(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn save_state(&self) {
|
||||||
|
let mut state_theme : usize = 0;
|
||||||
|
if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) {
|
||||||
|
state_theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state_tabs = vec![];
|
||||||
|
|
||||||
|
for tab in &self.tabs {
|
||||||
|
state_tabs.push(tab.path.clone());
|
||||||
|
}
|
||||||
|
let app_state = tools::AppState {
|
||||||
|
tabs: state_tabs,
|
||||||
|
theme: state_theme,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = tools::save_state(&app_state, SAVE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn move_through_tabs(&mut self, forward : bool) {
|
||||||
|
let new_index = if forward {
|
||||||
|
(self.selected_tab.to_index() + 1) % self.tabs.len()
|
||||||
|
} else {
|
||||||
|
self.selected_tab.to_index().checked_sub(1).unwrap_or(self.tabs.len() - 1)
|
||||||
|
};
|
||||||
|
self.selected_tab = tools::TabNumber::from_index(new_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn list_files(&mut self, ui: &mut egui::Ui, path: &Path) -> io::Result<()> {
|
||||||
|
if let Some(name) = path.file_name() {
|
||||||
|
if path.is_dir() {
|
||||||
|
egui::CollapsingHeader::new(name.to_string_lossy()).show(ui, |ui| {
|
||||||
|
let mut paths: Vec<_> = fs::read_dir(&path).expect("Failed to read dir").map(|r| r.unwrap()).collect();
|
||||||
|
|
||||||
|
paths.sort_by(|a, b| tools::sort_directories_first(a, b));
|
||||||
|
|
||||||
|
for result in paths {
|
||||||
|
let _ = self.list_files(ui, &result.path());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if ui.button(name.to_string_lossy()).clicked() {
|
||||||
|
self.open_file(Some(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn open_file(&mut self, path_option: Option<&Path>) {
|
||||||
|
if self.tabs.len() < MAX_TABS {
|
||||||
|
if let Some(path) = path_option {
|
||||||
|
self.tabs.push(tools::Tab::new(path.to_path_buf()));
|
||||||
|
} else {
|
||||||
|
self.tabs.push(tools::Tab::default());
|
||||||
|
}
|
||||||
|
self.selected_tab = tools::TabNumber::from_index(self.tabs.len() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn delete_tab(&mut self, index : usize) -> tools::TabNumber {
|
||||||
|
self.tabs.remove(index);
|
||||||
|
return tools::TabNumber::from_index(min(index, self.tabs.len() - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn toggle(&self, ui: &mut egui::Ui, display : bool, title : &str) -> bool {
|
||||||
|
let text = if display.clone() {
|
||||||
|
format!("hide {}", title)
|
||||||
|
} else {
|
||||||
|
format!("show {}", title)
|
||||||
|
};
|
||||||
|
|
||||||
|
if ui.add(egui::Button::new(text)).clicked() {
|
||||||
|
return !display
|
||||||
|
}
|
||||||
|
return display
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,254 +15,254 @@ 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 { break; }
|
||||||
if let Some(c) = it.next() {
|
if let Some(c) = it.next() {
|
||||||
char_pos += 1;
|
char_pos += 1;
|
||||||
byte_start += c.len_utf8();
|
byte_start += c.len_utf8();
|
||||||
}
|
}
|
||||||
else { break; }
|
else { break; }
|
||||||
}
|
}
|
||||||
char_pos = 0;
|
char_pos = 0;
|
||||||
let mut byte_end = byte_start;
|
let mut byte_end = byte_start;
|
||||||
loop {
|
loop {
|
||||||
if char_pos == len { break; }
|
if char_pos == len { break; }
|
||||||
if let Some(c) = it.next() {
|
if let Some(c) = it.next() {
|
||||||
char_pos += 1;
|
char_pos += 1;
|
||||||
byte_end += c.len_utf8();
|
byte_end += c.len_utf8();
|
||||||
}
|
}
|
||||||
else { break; }
|
else { break; }
|
||||||
}
|
}
|
||||||
&self[byte_start..byte_end]
|
&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(&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>) {
|
||||||
//let mut text_edit_output: Option<TextEditOutput> = None;
|
//let mut text_edit_output: Option<TextEditOutput> = None;
|
||||||
let mut code_editor = |ui: &mut egui::Ui| {
|
let mut code_editor = |ui: &mut egui::Ui| {
|
||||||
ui.horizontal_top(|h| {
|
ui.horizontal_top(|h| {
|
||||||
self.theme.modify_style(h, self.fontsize);
|
self.theme.modify_style(h, self.fontsize);
|
||||||
if self.numlines {
|
if self.numlines {
|
||||||
self.numlines_show(h, text);
|
self.numlines_show(h, text);
|
||||||
}
|
}
|
||||||
egui::ScrollArea::horizontal()
|
egui::ScrollArea::horizontal()
|
||||||
.id_source(format!("{}_inner_scroll", self.id))
|
.id_source(format!("{}_inner_scroll", self.id))
|
||||||
.show(h, |ui| {
|
.show(h, |ui| {
|
||||||
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 = highlight(ui.ctx(), self, string);
|
let layout_job = highlight(ui.ctx(), self, string);
|
||||||
ui.fonts(|f| f.layout_job(layout_job))
|
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;
|
||||||
|
@ -310,10 +310,10 @@ impl CodeEditor {
|
||||||
let mut start = min(cursor_range.primary.index, cursor_range.secondary.index);
|
let mut start = min(cursor_range.primary.index, cursor_range.secondary.index);
|
||||||
let end = max(cursor_range.primary.index, cursor_range.secondary.index);
|
let end = max(cursor_range.primary.index, cursor_range.secondary.index);
|
||||||
let extended = match end as isize + extend {
|
let extended = match end as isize + extend {
|
||||||
// Check for overflow or negative result
|
// Check for overflow or negative result
|
||||||
value if value < 0 => 0,
|
value if value < 0 => 0,
|
||||||
value => value as usize,
|
value => value as usize,
|
||||||
};
|
};
|
||||||
if start == end {
|
if start == end {
|
||||||
start = extended;
|
start = extended;
|
||||||
}
|
}
|
||||||
|
@ -331,22 +331,22 @@ impl CodeEditor {
|
||||||
*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(&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 substring = self.get_selection_substring(text.clone(), cursor_range.clone()).clone();
|
||||||
|
@ -418,7 +418,11 @@ impl CodeEditor {
|
||||||
fn new_line(&self, cursor_range : CCursorRange, text : String) -> (String, isize) {
|
fn new_line(&self, cursor_range : CCursorRange, text : String) -> (String, isize) {
|
||||||
let cursor = min(cursor_range.primary.index, cursor_range.secondary.index);
|
let cursor = min(cursor_range.primary.index, cursor_range.secondary.index);
|
||||||
|
|
||||||
let mut last_line_break = max(0, cursor - 1);
|
if cursor < 2 {
|
||||||
|
return (text.clone().to_string(), 1 as isize);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut last_line_break = cursor - 1;
|
||||||
|
|
||||||
while last_line_break > 0 && text.char_at(last_line_break) != '\n' {
|
while last_line_break > 0 && text.char_at(last_line_break) != '\n' {
|
||||||
last_line_break -= 1;
|
last_line_break -= 1;
|
||||||
|
@ -431,6 +435,6 @@ impl CodeEditor {
|
||||||
new_text.push_str(&"\t".repeat(new_indent_depth.clone()));
|
new_text.push_str(&"\t".repeat(new_indent_depth.clone()));
|
||||||
new_text.push_str(text.clone().slice((cursor + 1)..));
|
new_text.push_str(text.clone().slice((cursor + 1)..));
|
||||||
|
|
||||||
(new_text.clone().to_string(), (new_indent_depth + 1) as isize)
|
return (new_text.clone().to_string(), (new_indent_depth + 1) as isize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
137
src/calcifer_base.rs
Normal file
137
src/calcifer_base.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
// Hello there, Master
|
||||||
|
|
||||||
|
impl super::Calcifer {
|
||||||
|
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" {
|
||||||
|
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 handle_save_file(&mut self, path_option : Option<PathBuf>) {
|
||||||
|
if let Some(path) = path_option {
|
||||||
|
println!("File saved successfully at: {:?}", path);
|
||||||
|
self.tabs[self.selected_tab.to_index()].path = path;
|
||||||
|
self.tabs[self.selected_tab.to_index()].saved = true;
|
||||||
|
} else {
|
||||||
|
println!("File save failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_app_state(app_state: tools::AppState) -> Self {
|
||||||
|
let mut new = Self {
|
||||||
|
theme: DEFAULT_THEMES[min(app_state.theme, DEFAULT_THEMES.len() - 1)],
|
||||||
|
tabs: Vec::new(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
for path in app_state.tabs {
|
||||||
|
if path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string() != "untitled" {
|
||||||
|
new.open_file(Some(&path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if new.tabs == vec![] {
|
||||||
|
new.open_file(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_state(&self) {
|
||||||
|
let mut state_theme : usize = 0;
|
||||||
|
if let Some(theme) = DEFAULT_THEMES.iter().position(|&r| r == self.theme) {
|
||||||
|
state_theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state_tabs = vec![];
|
||||||
|
|
||||||
|
for tab in &self.tabs {
|
||||||
|
state_tabs.push(tab.path.clone());
|
||||||
|
}
|
||||||
|
let app_state = tools::AppState {
|
||||||
|
tabs: state_tabs,
|
||||||
|
theme: state_theme,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = tools::save_state(&app_state, super::SAVE_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_through_tabs(&mut self, forward : bool) {
|
||||||
|
let new_index = if forward {
|
||||||
|
(self.selected_tab.to_index() + 1) % self.tabs.len()
|
||||||
|
} else {
|
||||||
|
self.selected_tab.to_index().checked_sub(1).unwrap_or(self.tabs.len() - 1)
|
||||||
|
};
|
||||||
|
self.selected_tab = tools::TabNumber::from_index(new_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Sort the vector using the custom sorting function
|
||||||
|
paths.sort_by(|a, b| tools::sort_directories_first(a, b));
|
||||||
|
|
||||||
|
for result in paths {
|
||||||
|
//let result = path_result.expect("Failed to get path");
|
||||||
|
//let full_path = result.path();
|
||||||
|
let _ = self.list_files(ui, &result.path());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
//ui.label(name.to_string_lossy());
|
||||||
|
if ui.button(name.to_string_lossy()).clicked() {
|
||||||
|
self.open_file(Some(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_tab(&mut self, index : usize) -> tools::TabNumber {
|
||||||
|
self.tabs.remove(index);
|
||||||
|
return tools::TabNumber::from_index(min(index, self.tabs.len() - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle(&self, ui: &mut egui::Ui, display : bool, title : &str) -> bool {
|
||||||
|
let text = if display.clone() {
|
||||||
|
format!("hide {}", title)
|
||||||
|
} else {
|
||||||
|
format!("show {}", title)
|
||||||
|
};
|
||||||
|
|
||||||
|
if ui.add(egui::Button::new(text)).clicked() {
|
||||||
|
return !display
|
||||||
|
}
|
||||||
|
return display
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
{"tabs":["/home/penwing/Documents/notes/victory2.txt","/home/penwing/Documents/projects/rust/calcifer/src/calcifer/code_editor/themes/sonokai.rs"],"theme":4}
|
{"tabs":["/home/penwing/Documents/notes/victory2.txt","/home/penwing/Documents/projects/rust/calcifer/src/calcifer/code_editor/themes/sonokai.rs"],"theme":6}
|
|
@ -15,11 +15,10 @@ const TIME_LABELS : [&str; 5] = ["settings", "tree", "terminal", "tabs", "conten
|
||||||
const MAX_FPS : f32 = 30.0;
|
const MAX_FPS : f32 = 30.0;
|
||||||
const PATH_ROOT : &str = "/home/penwing/Documents/";
|
const PATH_ROOT : &str = "/home/penwing/Documents/";
|
||||||
const DISPLAY_PATH_DEPTH : usize = 3;
|
const DISPLAY_PATH_DEPTH : usize = 3;
|
||||||
|
const MAX_TABS : usize = 20;
|
||||||
|
|
||||||
|
|
||||||
fn main() -> Result<(), eframe::Error> {
|
fn main() -> Result<(), eframe::Error> {
|
||||||
tools::loaded();
|
|
||||||
|
|
||||||
let icon_data = tools::load_icon();
|
let icon_data = tools::load_icon();
|
||||||
|
|
||||||
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||||
|
@ -73,7 +72,7 @@ struct Calcifer {
|
||||||
impl Default for Calcifer {
|
impl Default for Calcifer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
selected_tab: tools::TabNumber::Zero,
|
selected_tab: tools::TabNumber::from_index(0),
|
||||||
tabs: vec![tools::Tab::default()],
|
tabs: vec![tools::Tab::default()],
|
||||||
|
|
||||||
command: String::new(),
|
command: String::new(),
|
||||||
|
@ -103,7 +102,7 @@ impl eframe::App for Calcifer {
|
||||||
let mut watch = time::Instant::now();
|
let mut watch = time::Instant::now();
|
||||||
|
|
||||||
if ctx.input( |i| i.key_pressed(egui::Key::T) && i.modifiers.ctrl) {
|
if ctx.input( |i| i.key_pressed(egui::Key::T) && i.modifiers.ctrl) {
|
||||||
self.indent_with_tabs();
|
self.tabs[self.selected_tab.to_index()].refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
|
162
src/tools/mod.rs
162
src/tools/mod.rs
|
@ -1,13 +1,19 @@
|
||||||
use std::{env, process::Command, cmp::Ordering, path::PathBuf, path::Path, fs::read_to_string, fs::write, path::Component, ffi::OsStr};
|
use std::{cmp::Ordering, path::PathBuf, path::Path, fs::read_to_string, fs::write, path::Component, ffi::OsStr};
|
||||||
use crate::calcifer::code_editor::Syntax;
|
use crate::calcifer::code_editor::Syntax;
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui::text_edit::CCursorRange;
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use crate::DISPLAY_PATH_DEPTH;
|
use crate::DISPLAY_PATH_DEPTH;
|
||||||
|
|
||||||
//pub mod themes;
|
//my tools;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
|
||||||
|
pub mod terminal;
|
||||||
|
pub use terminal::*;
|
||||||
|
|
||||||
|
pub mod tabs;
|
||||||
|
pub use tabs::*;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub trait View {
|
pub trait View {
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, tabs: &mut Vec<Tab>, selected_tab: &mut TabNumber);
|
fn ui(&mut self, ui: &mut egui::Ui, tabs: &mut Vec<Tab>, selected_tab: &mut TabNumber);
|
||||||
|
@ -15,124 +21,12 @@ pub trait View {
|
||||||
|
|
||||||
/// Something to view
|
/// Something to view
|
||||||
pub trait Demo {
|
pub trait Demo {
|
||||||
/// Is the demo enabled for this integraton?
|
|
||||||
fn is_enabled(&self, _ctx: &egui::Context) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `&'static` so we can also use it as a key to store open/close state.
|
|
||||||
fn name(&self) -> &str; //'static
|
fn name(&self) -> &str; //'static
|
||||||
|
|
||||||
/// Show windows, etc
|
/// Show windows, etc
|
||||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool, tabs: &mut Vec<Tab>, selected_tab: &mut TabNumber);
|
fn show(&mut self, ctx: &egui::Context, open: &mut bool, tabs: &mut Vec<Tab>, selected_tab: &mut TabNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub enum TabNumber {
|
|
||||||
None,
|
|
||||||
Open,
|
|
||||||
Zero,
|
|
||||||
One,
|
|
||||||
Two,
|
|
||||||
Three,
|
|
||||||
Four,
|
|
||||||
Five,
|
|
||||||
Six,
|
|
||||||
Seven,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl TabNumber {
|
|
||||||
pub fn from_index(n : usize) -> TabNumber {
|
|
||||||
match n {
|
|
||||||
0 => TabNumber::Zero,
|
|
||||||
1 => TabNumber::One,
|
|
||||||
2 => TabNumber::Two,
|
|
||||||
3 => TabNumber::Three,
|
|
||||||
4 => TabNumber::Four,
|
|
||||||
5 => TabNumber::Five,
|
|
||||||
6 => TabNumber::Six,
|
|
||||||
7 => TabNumber::Seven,
|
|
||||||
_ => TabNumber::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn to_index(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
TabNumber::Zero => 0,
|
|
||||||
TabNumber::One => 1,
|
|
||||||
TabNumber::Two => 2,
|
|
||||||
TabNumber::Three => 3,
|
|
||||||
TabNumber::Four => 4,
|
|
||||||
TabNumber::Five => 5,
|
|
||||||
TabNumber::Six => 6,
|
|
||||||
TabNumber::Seven => 7,
|
|
||||||
_ => 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
|
||||||
pub struct Tab {
|
|
||||||
pub path : PathBuf,
|
|
||||||
pub code : String,
|
|
||||||
pub language : String,
|
|
||||||
pub saved : bool,
|
|
||||||
pub scroll_offset : f32,
|
|
||||||
pub last_cursor : Option<CCursorRange>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Tab {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
path: "untitled".into(),
|
|
||||||
code: "// Hello there, Master".into(),
|
|
||||||
language: "rs".into(),
|
|
||||||
saved: false,
|
|
||||||
scroll_offset: 0.0,
|
|
||||||
last_cursor: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Tab {
|
|
||||||
pub fn get_name(&self) -> String {
|
|
||||||
self.path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
|
||||||
pub struct CommandEntry {
|
|
||||||
pub env: String,
|
|
||||||
pub command: String,
|
|
||||||
pub output: String,
|
|
||||||
pub error: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandEntry {
|
|
||||||
pub fn new(command: String) -> Self {
|
|
||||||
CommandEntry {
|
|
||||||
env: format_path(&env::current_dir().expect("Could not find Shell Environnment")),
|
|
||||||
command,
|
|
||||||
output: String::new(),
|
|
||||||
error: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&mut self) -> Self {
|
|
||||||
let output = Command::new("sh")
|
|
||||||
.arg("-c")
|
|
||||||
.arg(self.command.clone())
|
|
||||||
.output()
|
|
||||||
.expect("failed to execute process");
|
|
||||||
self.output = (&String::from_utf8_lossy(&output.stdout)).trim_end_matches('\n').to_string();
|
|
||||||
self.error = (&String::from_utf8_lossy(&output.stderr)).trim_end_matches('\n').to_string();
|
|
||||||
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
@ -158,10 +52,6 @@ pub fn load_state(file_path: &str) -> Result<AppState, std::io::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn loaded() {
|
|
||||||
println!("Tools loaded");
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
||||||
|
@ -190,40 +80,6 @@ pub fn to_syntax(language : &str) -> Syntax {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn run_command(command: String) -> CommandEntry {
|
|
||||||
let mut entry = CommandEntry::new(command);
|
|
||||||
|
|
||||||
if entry.command.len() < 2 {
|
|
||||||
return entry.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
if &entry.command[..2] != "cd" {
|
|
||||||
return entry.run()
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.command.len() < 4 {
|
|
||||||
entry.error = "Invalid cd, should provide path".to_string();
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
|
|
||||||
let path_append = entry.command[3..].replace("~", "/home/penwing");
|
|
||||||
let path = Path::new(&path_append);
|
|
||||||
|
|
||||||
if format!("{}", path.display()) == "/" {
|
|
||||||
entry.error = "Root access denied".to_string();
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
|
|
||||||
if env::set_current_dir(path).is_ok() {
|
|
||||||
entry.output = format!("Moved to : {}", path.display());
|
|
||||||
} else {
|
|
||||||
entry.error = format!("Could not find path : {}", path.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use crate::tools::{View, Demo, Tab, TabNumber};
|
use crate::tools::{View, Demo, tabs::Tab, tabs::TabNumber};
|
||||||
use std::{cmp::min};
|
use std::{cmp::min};
|
||||||
use crate::RED;
|
use crate::RED;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ pub struct Selection {
|
||||||
impl Default for Selection {
|
impl Default for Selection {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
tab: TabNumber::Zero,
|
tab: TabNumber::from_index(0),
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
}
|
}
|
||||||
|
|
73
src/tools/tabs.rs
Normal file
73
src/tools/tabs.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use std::{fs::read_to_string, path::PathBuf};
|
||||||
|
use eframe::egui::text_edit::CCursorRange;
|
||||||
|
|
||||||
|
use crate::MAX_TABS;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum TabNumber {
|
||||||
|
Open,
|
||||||
|
Number(u8), // Using a range for numeric values
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabNumber {
|
||||||
|
pub fn from_index(n: usize) -> TabNumber {
|
||||||
|
match n {
|
||||||
|
0..=MAX_TABS => TabNumber::Number(n as u8),
|
||||||
|
_ => TabNumber::Number(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_index(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
TabNumber::Number(n) => *n as usize,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct Tab {
|
||||||
|
pub path : PathBuf,
|
||||||
|
pub code : String,
|
||||||
|
pub language : String,
|
||||||
|
pub saved : bool,
|
||||||
|
pub scroll_offset : f32,
|
||||||
|
pub last_cursor : Option<CCursorRange>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Tab {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
path: "untitled".into(),
|
||||||
|
code: "// Hello there, Master".into(),
|
||||||
|
language: "rs".into(),
|
||||||
|
saved: false,
|
||||||
|
scroll_offset: 0.0,
|
||||||
|
last_cursor: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Tab {
|
||||||
|
pub fn new(path : PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
path: path.clone().into(),
|
||||||
|
code: read_to_string(path.clone()).expect("Not able to read the file").replace(&" ".repeat(4), "\t"),
|
||||||
|
language: path.to_str().unwrap().split('.').last().unwrap().into(),
|
||||||
|
saved: true,
|
||||||
|
scroll_offset: 0.0,
|
||||||
|
last_cursor: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
pub fn get_name(&self) -> String {
|
||||||
|
self.path.file_name().expect("Could not get Tab Name").to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh(&mut self) {
|
||||||
|
self.code = read_to_string(self.path.clone()).expect("Not able to read the file").replace(&" ".repeat(4), "\t");
|
||||||
|
println!("refreshed {}", self.path.display());
|
||||||
|
}
|
||||||
|
}
|
68
src/tools/terminal.rs
Normal file
68
src/tools/terminal.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use crate::tools::format_path;
|
||||||
|
use std::{process::Command, env, path::Path};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct CommandEntry {
|
||||||
|
pub env: String,
|
||||||
|
pub command: String,
|
||||||
|
pub output: String,
|
||||||
|
pub error: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl CommandEntry {
|
||||||
|
pub fn new(command: String) -> Self {
|
||||||
|
CommandEntry {
|
||||||
|
env: format_path(&env::current_dir().expect("Could not find Shell Environnment")),
|
||||||
|
command,
|
||||||
|
output: String::new(),
|
||||||
|
error: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) -> Self {
|
||||||
|
let output = Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(self.command.clone())
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute process");
|
||||||
|
self.output = (&String::from_utf8_lossy(&output.stdout)).trim_end_matches('\n').to_string();
|
||||||
|
self.error = (&String::from_utf8_lossy(&output.stderr)).trim_end_matches('\n').to_string();
|
||||||
|
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run_command(command: String) -> CommandEntry {
|
||||||
|
let mut entry = CommandEntry::new(command);
|
||||||
|
|
||||||
|
if entry.command.len() < 2 {
|
||||||
|
return entry.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
if &entry.command[..2] != "cd" {
|
||||||
|
return entry.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.command.len() < 4 {
|
||||||
|
entry.error = "Invalid cd, should provide path".to_string();
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
let path_append = entry.command[3..].replace("~", "/home/penwing");
|
||||||
|
let path = Path::new(&path_append);
|
||||||
|
|
||||||
|
if format!("{}", path.display()) == "/" {
|
||||||
|
entry.error = "Root access denied".to_string();
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
if env::set_current_dir(path).is_ok() {
|
||||||
|
entry.output = format!("Moved to : {}", path.display());
|
||||||
|
} else {
|
||||||
|
entry.error = format!("Could not find path : {}", path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry
|
||||||
|
}
|
Loading…
Reference in a new issue