2024-01-15 09:14:27 +01:00
2024-01-17 21:03:41 +01:00
mod tools ;
2024-01-15 09:14:27 +01:00
2024-01-14 10:56:21 +01:00
use eframe ::egui ;
2024-01-20 15:23:57 +01:00
use egui_code_editor ::{ CodeEditor , ColorTheme } ;
2024-01-21 10:24:16 +01:00
use std ::{ path ::Path , path ::PathBuf , fs , io , env , cmp ::max , cmp ::min , sync ::Arc } ;
2024-01-16 08:05:46 +01:00
const TERMINAL_HEIGHT : f32 = 200.0 ;
2024-01-19 17:20:15 +01:00
const RED : egui ::Color32 = egui ::Color32 ::from_rgb ( 235 , 108 , 99 ) ;
2024-01-20 19:08:23 +01:00
const HISTORY_LENGTH : usize = 2 ;
2024-01-15 09:14:27 +01:00
2024-01-14 10:56:21 +01:00
fn main ( ) -> Result < ( ) , eframe ::Error > {
2024-01-17 21:03:41 +01:00
tools ::loaded ( ) ;
2024-01-21 10:24:16 +01:00
let icon_data = tools ::load_icon ( ) ;
2024-01-14 10:56:21 +01:00
env_logger ::init ( ) ; // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe ::NativeOptions {
viewport : egui ::ViewportBuilder ::default ( )
2024-01-15 10:06:20 +01:00
. with_inner_size ( [ 1200.0 , 800.0 ] )
2024-01-21 10:24:16 +01:00
. with_icon ( Arc ::new ( icon_data ) ) ,
2024-01-14 10:56:21 +01:00
.. Default ::default ( )
} ;
eframe ::run_native (
" Calcifer " ,
options ,
2024-01-20 15:23:57 +01:00
Box ::new ( | _cc | Box ::< Calcifer > ::default ( ) ) ,
2024-01-14 10:56:21 +01:00
)
}
2024-01-17 16:20:22 +01:00
2024-01-20 15:23:57 +01:00
struct Calcifer {
2024-01-19 19:21:16 +01:00
selected_tab : tools ::TabNumber ,
tabs : Vec < tools ::Tab > ,
2024-01-16 11:07:53 +01:00
command : String ,
2024-01-19 17:20:15 +01:00
command_history : Vec < tools ::CommandEntry > ,
2024-01-20 15:23:57 +01:00
theme : ColorTheme ,
2024-01-14 11:50:25 +01:00
}
2024-01-16 12:53:03 +01:00
2024-01-20 15:23:57 +01:00
impl Default for Calcifer {
2024-01-14 11:50:25 +01:00
fn default ( ) -> Self {
Self {
2024-01-20 15:23:57 +01:00
selected_tab : tools ::TabNumber ::Zero ,
2024-01-20 19:08:23 +01:00
tabs : vec ! [ tools ::Tab ::default ( ) ] ,
2024-01-16 11:07:53 +01:00
command : " " . into ( ) ,
2024-01-19 17:20:15 +01:00
command_history : Vec ::new ( ) ,
2024-01-20 15:23:57 +01:00
theme : tools ::themes ::CustomColorTheme ::fire ( )
2024-01-14 11:50:25 +01:00
}
}
}
2024-01-15 10:06:20 +01:00
2024-01-20 15:23:57 +01:00
impl eframe ::App for Calcifer {
2024-01-14 10:56:21 +01:00
fn update ( & mut self , ctx : & egui ::Context , _frame : & mut eframe ::Frame ) {
2024-01-20 19:08:23 +01:00
if ctx . input ( | i | i . key_pressed ( egui ::Key ::S ) & & i . modifiers . ctrl ) {
if let Some ( path ) = self . save_tab ( ) {
println! ( " File saved successfully at: {:?} " , path ) ;
self . tabs [ self . selected_tab . to_n ( ) ] . path = path ;
2024-01-21 10:24:16 +01:00
self . tabs [ self . selected_tab . to_n ( ) ] . saved = true ;
2024-01-20 19:08:23 +01:00
} else {
println! ( " File save failed. " ) ;
}
}
if ctx . input ( | i | i . key_pressed ( egui ::Key ::S ) & & i . modifiers . ctrl & & i . modifiers . shift ) {
if let Some ( path ) = self . save_tab_as ( ) {
println! ( " File saved successfully at: {:?} " , path ) ;
self . tabs [ self . selected_tab . to_n ( ) ] . path = path ;
2024-01-21 10:24:16 +01:00
self . tabs [ self . selected_tab . to_n ( ) ] . saved = true ;
2024-01-20 19:08:23 +01:00
} else {
println! ( " File save failed. " ) ;
}
}
if ctx . input ( | i | i . key_pressed ( egui ::Key ::Z ) & & i . modifiers . ctrl ) {
self . undo ( ) ;
}
2024-01-20 15:23:57 +01:00
self . draw_settings ( ctx ) ;
2024-01-17 16:20:22 +01:00
self . draw_tree_panel ( ctx ) ;
self . draw_terminal_panel ( ctx ) ;
2024-01-19 19:21:16 +01:00
self . draw_tab_panel ( ctx ) ;
2024-01-20 15:23:57 +01:00
self . draw_content_panel ( ctx ) ;
2024-01-20 19:08:23 +01:00
}
2024-01-17 16:20:22 +01:00
}
2024-01-20 19:08:23 +01:00
2024-01-20 15:23:57 +01:00
impl Calcifer {
fn draw_settings ( & mut self , ctx : & egui ::Context ) {
egui ::TopBottomPanel ::top ( " settings " )
. resizable ( false )
. show ( ctx , | ui | {
ui . horizontal ( | ui | {
ui . label ( " Theme " ) ;
egui ::ComboBox ::from_label ( " " )
. selected_text ( format! ( " {} " , self . theme . name ) )
. show_ui ( ui , | ui | {
ui . style_mut ( ) . wrap = Some ( false ) ;
ui . set_min_width ( 60.0 ) ;
ui . selectable_value ( & mut self . theme , ColorTheme ::SONOKAI , " Sonokai " ) ;
ui . selectable_value ( & mut self . theme , ColorTheme ::AYU_DARK , " Ayu Dark " ) ;
ui . selectable_value ( & mut self . theme , ColorTheme ::AYU_MIRAGE , " Ayu Mirage " ) ;
ui . selectable_value ( & mut self . theme , ColorTheme ::GITHUB_DARK , " Github Dark " ) ;
ui . selectable_value ( & mut self . theme , ColorTheme ::GRUVBOX , " Gruvbox " ) ;
ui . selectable_value ( & mut self . theme , tools ::themes ::CustomColorTheme ::fire ( ) , " Fire " ) ;
2024-01-20 17:31:53 +01:00
ui . selectable_value ( & mut self . theme , tools ::themes ::CustomColorTheme ::ash ( ) , " Ash " ) ;
2024-01-20 15:23:57 +01:00
} ) ;
} ) ;
} ) ;
}
2024-01-19 17:20:15 +01:00
fn draw_tree_panel ( & mut self , ctx : & egui ::Context ) {
2024-01-17 16:20:22 +01:00
egui ::SidePanel ::left ( " file_tree_panel " ) . show ( ctx , | ui | {
2024-01-19 17:20:15 +01:00
ui . heading ( " Bookshelf " ) ;
2024-01-20 15:23:57 +01:00
if ui . add ( egui ::Button ::new ( " open file " ) ) . clicked ( ) {
if let Some ( path ) = rfd ::FileDialog ::new ( ) . pick_file ( ) {
self . selected_tab = self . open_file ( & path ) ;
}
}
2024-01-17 20:55:50 +01:00
ui . separator ( ) ;
2024-01-19 17:20:15 +01:00
let _ = self . list_files ( ui , Path ::new ( " /home/penwing/Documents/ " ) ) ;
ui . separator ( ) ;
2024-01-16 11:07:53 +01:00
} ) ;
2024-01-17 16:20:22 +01:00
}
fn draw_terminal_panel ( & mut self , ctx : & egui ::Context ) {
egui ::TopBottomPanel ::bottom ( " terminal " )
2024-01-19 17:20:15 +01:00
. default_height ( TERMINAL_HEIGHT . clone ( ) )
. min_height ( 0.0 )
2024-01-17 16:20:22 +01:00
. show ( ctx , | ui | {
ui . with_layout ( egui ::Layout ::bottom_up ( egui ::Align ::LEFT ) , | ui | {
ui . label ( " " ) ;
ui . horizontal ( | ui | {
2024-01-21 10:24:16 +01:00
ui . style_mut ( ) . visuals . extreme_bg_color = egui ::Color32 ::from_hex ( self . theme . bg ) . expect ( " Could not convert color " ) ;
2024-01-17 16:20:22 +01:00
let Self { command , .. } = self ;
2024-01-19 17:20:15 +01:00
ui . label ( format! ( " {} > " , env ::current_dir ( ) . expect ( " Could not find Shell Environnment " ) . file_name ( ) . expect ( " Could not get Shell Environnment Name " ) . to_string_lossy ( ) . to_string ( ) ) ) ;
2024-01-17 16:20:22 +01:00
let response = ui . add ( egui ::TextEdit ::singleline ( command ) . desired_width ( f32 ::INFINITY ) . lock_focus ( true ) ) ;
if response . lost_focus ( ) & & ctx . input ( | i | i . key_pressed ( egui ::Key ::Enter ) ) {
2024-01-19 17:20:15 +01:00
self . command_history . push ( tools ::run_command ( self . command . clone ( ) ) ) ;
2024-01-17 16:20:22 +01:00
self . command = " " . into ( ) ;
response . request_focus ( ) ;
}
} ) ;
2024-01-19 17:20:15 +01:00
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 & self . command_history {
ui . label ( format! ( " {} > {} " , entry . env , entry . command ) ) ;
ui . end_row ( ) ;
if entry . output ! = " " {
ui . label ( & entry . output ) ;
ui . end_row ( ) ;
}
if entry . error ! = " " {
ui . colored_label ( RED , & entry . error ) ;
ui . end_row ( ) ;
}
}
} ) ;
} ) ;
2024-01-17 16:20:22 +01:00
} ) ;
} ) ;
} ) ;
}
2024-01-19 19:21:16 +01:00
fn draw_tab_panel ( & mut self , ctx : & egui ::Context ) {
egui ::TopBottomPanel ::top ( " tabs " )
. resizable ( false )
. show ( ctx , | ui | {
2024-01-20 19:08:23 +01:00
ui . horizontal ( | ui | {
2024-01-21 10:24:16 +01:00
ui . style_mut ( ) . visuals . selection . bg_fill = egui ::Color32 ::from_hex ( self . theme . functions ) . expect ( " Could not convert color " ) ;
ui . style_mut ( ) . visuals . hyperlink_color = egui ::Color32 ::from_hex ( self . theme . functions ) . expect ( " Could not convert color " ) ;
2024-01-20 17:31:53 +01:00
for ( index , tab ) in self . tabs . clone ( ) . iter ( ) . enumerate ( ) {
2024-01-20 19:08:23 +01:00
let mut title = tab . get_name ( ) ;
if ! tab . saved {
title + = " ~ " ;
}
2024-01-21 10:24:16 +01:00
if self . selected_tab = = tools ::TabNumber ::from_n ( index ) {
ui . style_mut ( ) . visuals . override_text_color = Some ( egui ::Color32 ::from_hex ( self . theme . bg ) . expect ( " Could not convert color " ) ) ;
}
2024-01-20 19:08:23 +01:00
ui . selectable_value ( & mut self . selected_tab , tools ::TabNumber ::from_n ( index ) , title ) ;
2024-01-21 10:24:16 +01:00
ui . style_mut ( ) . visuals . override_text_color = None ;
2024-01-20 17:31:53 +01:00
if ui . link ( " X " ) . clicked ( ) {
self . selected_tab = self . delete_tab ( index ) ;
}
ui . separator ( ) ;
2024-01-19 19:21:16 +01:00
}
2024-01-20 15:23:57 +01:00
if tools ::TabNumber ::from_n ( self . tabs . len ( ) ) ! = tools ::TabNumber ::None {
2024-01-19 19:21:16 +01:00
ui . selectable_value ( & mut self . selected_tab , tools ::TabNumber ::Open , " + " ) ;
}
if self . selected_tab = = tools ::TabNumber ::Open {
2024-01-20 15:23:57 +01:00
self . selected_tab = self . new_tab ( ) ;
2024-01-19 19:21:16 +01:00
}
} ) ;
} ) ;
}
2024-01-17 16:20:22 +01:00
2024-01-20 15:23:57 +01:00
fn draw_content_panel ( & mut self , ctx : & egui ::Context ) {
2024-01-14 10:56:21 +01:00
egui ::CentralPanel ::default ( ) . show ( ctx , | ui | {
2024-01-20 15:23:57 +01:00
ui . horizontal ( | ui | {
ui . label ( " Picked file: " ) ;
ui . monospace ( self . tabs [ self . selected_tab . to_n ( ) ] . path . to_string_lossy ( ) . to_string ( ) ) ;
} ) ;
if self . selected_tab = = tools ::TabNumber ::None {
return
}
self . draw_code_file ( ui ) ;
2024-01-15 09:14:27 +01:00
} ) ;
2024-01-14 10:56:21 +01:00
}
2024-01-19 17:20:15 +01:00
2024-01-20 15:23:57 +01:00
fn draw_code_file ( & mut self , ui : & mut egui ::Ui ) {
2024-01-20 19:08:23 +01:00
let current_tab = & mut self . tabs [ self . selected_tab . to_n ( ) ] ;
let lines = current_tab . code . chars ( ) . filter ( | & c | c = = '\n' ) . count ( ) + 1 ;
2024-01-20 15:23:57 +01:00
egui ::ScrollArea ::vertical ( ) . show ( ui , | ui | {
CodeEditor ::default ( )
. id_source ( " code editor " )
. with_rows ( max ( 80 , lines ) )
. with_fontsize ( 14.0 )
. with_theme ( self . theme )
2024-01-20 19:08:23 +01:00
. with_syntax ( tools ::to_syntax ( & current_tab . language ) )
2024-01-20 15:23:57 +01:00
. with_numlines ( true )
2024-01-20 19:08:23 +01:00
. show ( ui , & mut current_tab . code ) ;
2024-01-20 15:23:57 +01:00
} ) ;
2024-01-20 19:08:23 +01:00
if current_tab . history . len ( ) < 1 {
current_tab . history . push ( current_tab . code . clone ( ) ) ;
}
if & current_tab . code ! = current_tab . history . last ( ) . expect ( " There should be an history " ) {
current_tab . history . push ( current_tab . code . clone ( ) ) ;
current_tab . saved = false ;
if current_tab . history . len ( ) > HISTORY_LENGTH {
current_tab . history . remove ( 0 ) ;
}
}
2024-01-20 15:23:57 +01:00
}
2024-01-19 17:20:15 +01:00
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 ( ) {
2024-01-19 19:21:16 +01:00
self . selected_tab = self . open_file ( & path ) ;
2024-01-19 17:20:15 +01:00
}
}
}
Ok ( ( ) )
}
2024-01-19 19:21:16 +01:00
fn open_file ( & mut self , path : & Path ) -> tools ::TabNumber {
2024-01-20 15:23:57 +01:00
if tools ::TabNumber ::from_n ( self . tabs . len ( ) ) = = tools ::TabNumber ::None {
2024-01-19 19:21:16 +01:00
return tools ::TabNumber ::None
}
let new_tab = tools ::Tab {
path : path . into ( ) ,
2024-01-20 15:23:57 +01:00
code : fs ::read_to_string ( path ) . expect ( " Not able to read the file " ) ,
language : path . to_str ( ) . unwrap ( ) . split ( '.' ) . last ( ) . unwrap ( ) . into ( ) ,
2024-01-20 19:08:23 +01:00
saved : true ,
history : vec ! [ ] ,
2024-01-19 19:21:16 +01:00
} ;
self . tabs . push ( new_tab ) ;
2024-01-20 15:23:57 +01:00
return tools ::TabNumber ::from_n ( self . tabs . len ( ) - 1 )
}
fn new_tab ( & mut self ) -> tools ::TabNumber {
2024-01-20 19:08:23 +01:00
self . tabs . push ( tools ::Tab ::default ( ) ) ;
2024-01-20 15:23:57 +01:00
return tools ::TabNumber ::from_n ( self . tabs . len ( ) - 1 )
2024-01-19 17:20:15 +01:00
}
2024-01-20 17:31:53 +01:00
fn delete_tab ( & mut self , index : usize ) -> tools ::TabNumber {
self . tabs . remove ( index ) ;
return tools ::TabNumber ::from_n ( min ( index , self . tabs . len ( ) - 1 ) )
}
2024-01-20 19:08:23 +01:00
fn save_tab ( & self ) -> Option < PathBuf > {
if self . tabs [ self . selected_tab . to_n ( ) ] . 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_n ( ) ] . path , & self . tabs [ self . selected_tab . to_n ( ) ] . code ) {
eprintln! ( " Error writing file: {} " , err ) ;
return None ;
}
return Some ( self . tabs [ self . selected_tab . to_n ( ) ] . path . clone ( ) )
}
}
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_n ( ) ] . code ) {
eprintln! ( " Error writing file: {} " , err ) ;
return None ;
}
return Some ( path ) ;
}
return None
}
fn undo ( & mut self ) {
let current_tab = & mut self . tabs [ self . selected_tab . to_n ( ) ] ;
if current_tab . history . len ( ) < 2 {
return
}
current_tab . code = current_tab . history [ current_tab . history . len ( ) - 2 ] . clone ( ) ;
current_tab . history . pop ( ) ;
}
2024-01-14 10:56:21 +01:00
}