master #7
@ -8,9 +8,11 @@ eframe = "0.32"
|
|||||||
egui = "0.32"
|
egui = "0.32"
|
||||||
egui_extras = { version = "0.32", features = ["syntect"] }
|
egui_extras = { version = "0.32", features = ["syntect"] }
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
rfd = "0.15.4"
|
rfd = "0.15.4"
|
||||||
toml = "0.9.2"
|
toml = "0.9.2"
|
||||||
dirs = "6.0"
|
dirs = "6.0"
|
||||||
libc = "0.2.174"
|
libc = "0.2.174"
|
||||||
syntect = "5.2.0"
|
syntect = "5.2.0"
|
||||||
plist = "1.7.4"
|
plist = "1.7.4"
|
||||||
|
diffy = "0.4.2"
|
||||||
|
|||||||
@ -5,6 +5,8 @@ use super::theme::Theme;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
#[serde(default = "default_state_cache")]
|
||||||
|
pub state_cache: bool,
|
||||||
#[serde(default = "default_auto_hide_toolbar")]
|
#[serde(default = "default_auto_hide_toolbar")]
|
||||||
pub auto_hide_toolbar: bool,
|
pub auto_hide_toolbar: bool,
|
||||||
#[serde(default = "default_hide_tab_bar")]
|
#[serde(default = "default_hide_tab_bar")]
|
||||||
@ -26,6 +28,10 @@ pub struct Config {
|
|||||||
// pub vim_mode: bool,
|
// pub vim_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_state_cache() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn default_auto_hide_toolbar() -> bool {
|
fn default_auto_hide_toolbar() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@ -54,6 +60,7 @@ fn default_syntax_highlighting() -> bool {
|
|||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
state_cache: default_state_cache(),
|
||||||
auto_hide_toolbar: default_auto_hide_toolbar(),
|
auto_hide_toolbar: default_auto_hide_toolbar(),
|
||||||
hide_tab_bar: default_hide_tab_bar(),
|
hide_tab_bar: default_hide_tab_bar(),
|
||||||
show_line_numbers: default_show_line_numbers(),
|
show_line_numbers: default_show_line_numbers(),
|
||||||
|
|||||||
@ -5,6 +5,7 @@ mod editor;
|
|||||||
mod find;
|
mod find;
|
||||||
mod lifecycle;
|
mod lifecycle;
|
||||||
mod processing;
|
mod processing;
|
||||||
|
mod state_cache;
|
||||||
mod tabs;
|
mod tabs;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ use crate::app::theme;
|
|||||||
impl TextEditor {
|
impl TextEditor {
|
||||||
pub fn from_config(config: Config) -> Self {
|
pub fn from_config(config: Config) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
state_cache: config.state_cache,
|
||||||
show_line_numbers: config.show_line_numbers,
|
show_line_numbers: config.show_line_numbers,
|
||||||
word_wrap: config.word_wrap,
|
word_wrap: config.word_wrap,
|
||||||
auto_hide_toolbar: config.auto_hide_toolbar,
|
auto_hide_toolbar: config.auto_hide_toolbar,
|
||||||
@ -20,6 +21,12 @@ impl TextEditor {
|
|||||||
|
|
||||||
pub fn from_config_with_context(config: Config, cc: &eframe::CreationContext<'_>) -> Self {
|
pub fn from_config_with_context(config: Config, cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
let mut editor = Self::from_config(config);
|
let mut editor = Self::from_config(config);
|
||||||
|
|
||||||
|
// Load state cache if enabled
|
||||||
|
if let Err(e) = editor.load_state_cache() {
|
||||||
|
eprintln!("Failed to load state cache: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
theme::apply(editor.theme, &cc.egui_ctx);
|
theme::apply(editor.theme, &cc.egui_ctx);
|
||||||
|
|
||||||
cc.egui_ctx.options_mut(|o| o.zoom_with_keyboard = false);
|
cc.egui_ctx.options_mut(|o| o.zoom_with_keyboard = false);
|
||||||
@ -46,6 +53,7 @@ impl TextEditor {
|
|||||||
|
|
||||||
pub fn get_config(&self) -> Config {
|
pub fn get_config(&self) -> Config {
|
||||||
Config {
|
Config {
|
||||||
|
state_cache: self.state_cache,
|
||||||
auto_hide_toolbar: self.auto_hide_toolbar,
|
auto_hide_toolbar: self.auto_hide_toolbar,
|
||||||
show_line_numbers: self.show_line_numbers,
|
show_line_numbers: self.show_line_numbers,
|
||||||
hide_tab_bar: self.hide_tab_bar,
|
hide_tab_bar: self.hide_tab_bar,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ impl Default for TextEditor {
|
|||||||
Self {
|
Self {
|
||||||
tabs: vec![Tab::new_empty(1)],
|
tabs: vec![Tab::new_empty(1)],
|
||||||
active_tab_index: 0,
|
active_tab_index: 0,
|
||||||
|
state_cache: false,
|
||||||
tab_counter: 1,
|
tab_counter: 1,
|
||||||
show_about: false,
|
show_about: false,
|
||||||
show_shortcuts: false,
|
show_shortcuts: false,
|
||||||
|
|||||||
@ -34,6 +34,7 @@ pub struct TextEditor {
|
|||||||
pub(crate) tabs: Vec<Tab>,
|
pub(crate) tabs: Vec<Tab>,
|
||||||
pub(crate) active_tab_index: usize,
|
pub(crate) active_tab_index: usize,
|
||||||
pub(crate) tab_counter: usize,
|
pub(crate) tab_counter: usize,
|
||||||
|
pub(crate) state_cache: bool,
|
||||||
pub(crate) show_about: bool,
|
pub(crate) show_about: bool,
|
||||||
pub(crate) show_shortcuts: bool,
|
pub(crate) show_shortcuts: bool,
|
||||||
pub(crate) show_find: bool,
|
pub(crate) show_find: bool,
|
||||||
|
|||||||
@ -15,16 +15,22 @@ impl TextEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_quit(&mut self, ctx: &egui::Context) {
|
pub fn request_quit(&mut self, ctx: &egui::Context) {
|
||||||
if self.has_unsaved_changes() {
|
if self.has_unsaved_changes() && !self.state_cache {
|
||||||
self.pending_unsaved_action = Some(UnsavedAction::Quit);
|
self.pending_unsaved_action = Some(UnsavedAction::Quit);
|
||||||
} else {
|
} else {
|
||||||
self.clean_quit_requested = true;
|
self.clean_quit_requested = true;
|
||||||
|
if let Err(e) = self.save_state_cache() {
|
||||||
|
eprintln!("Failed to save state cache: {e}");
|
||||||
|
}
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn force_quit(&mut self, ctx: &egui::Context) {
|
pub fn force_quit(&mut self, ctx: &egui::Context) {
|
||||||
self.force_quit_confirmed = true;
|
self.force_quit_confirmed = true;
|
||||||
|
if let Err(e) = self.save_state_cache() {
|
||||||
|
eprintln!("Failed to save state cache: {e}");
|
||||||
|
}
|
||||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,49 +71,40 @@ impl TextEditor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let visuals = &ctx.style().visuals;
|
let visuals = &ctx.style().visuals;
|
||||||
|
let error_color = visuals.error_fg_color;
|
||||||
|
|
||||||
egui::Window::new(title)
|
egui::Window::new(title)
|
||||||
.collapsible(false)
|
.collapsible(false)
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
|
.anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
|
||||||
.frame(egui::Frame {
|
|
||||||
fill: visuals.window_fill,
|
|
||||||
stroke: visuals.window_stroke,
|
|
||||||
corner_radius: egui::CornerRadius::same(8),
|
|
||||||
shadow: visuals.window_shadow,
|
|
||||||
inner_margin: egui::Margin::same(16),
|
|
||||||
outer_margin: egui::Margin::same(0),
|
|
||||||
})
|
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.vertical(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.label(egui::RichText::new(&confirmation_text).size(14.0));
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
ui.label(egui::RichText::new(&confirmation_text).size(14.0));
|
||||||
|
ui.add_space(4.0);
|
||||||
|
|
||||||
for file in &files_to_list {
|
for file in &files_to_list {
|
||||||
ui.label(egui::RichText::new(format!("• {file}")).size(18.0).weak());
|
ui.label(egui::RichText::new(file).size(12.0).color(error_color));
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let cancel_fill = ui.visuals().widgets.inactive.bg_fill;
|
|
||||||
let cancel_stroke = ui.visuals().widgets.inactive.bg_stroke;
|
|
||||||
let cancel_button = egui::Button::new("Cancel")
|
|
||||||
.fill(cancel_fill)
|
|
||||||
.stroke(cancel_stroke);
|
|
||||||
|
|
||||||
if ui.add(cancel_button).clicked() {
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Cancel").clicked() {
|
||||||
cancel_action = true;
|
cancel_action = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
let destructive_color = ui.visuals().error_fg_color;
|
if ui
|
||||||
let confirm_button = egui::Button::new(&button_text)
|
.button(egui::RichText::new(&button_text).color(error_color))
|
||||||
.fill(destructive_color)
|
.clicked()
|
||||||
.stroke(egui::Stroke::new(1.0, destructive_color));
|
{
|
||||||
|
close_action_now = Some(action.to_owned());
|
||||||
if ui.add(confirm_button).clicked() {
|
|
||||||
close_action_now = Some(action);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ui.add_space(8.0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -117,9 +114,17 @@ impl TextEditor {
|
|||||||
|
|
||||||
if let Some(action) = close_action_now {
|
if let Some(action) = close_action_now {
|
||||||
match action {
|
match action {
|
||||||
UnsavedAction::Quit => self.force_quit(ctx),
|
UnsavedAction::Quit => {
|
||||||
|
if let Err(e) = self.save_state_cache() {
|
||||||
|
eprintln!("Failed to save state cache: {e}");
|
||||||
|
}
|
||||||
|
self.force_quit(ctx);
|
||||||
|
}
|
||||||
UnsavedAction::CloseTab(tab_index) => {
|
UnsavedAction::CloseTab(tab_index) => {
|
||||||
self.close_tab(tab_index);
|
self.close_tab(tab_index);
|
||||||
|
if let Err(e) = self.save_state_cache() {
|
||||||
|
eprintln!("Failed to save state cache: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.pending_unsaved_action = None;
|
self.pending_unsaved_action = None;
|
||||||
|
|||||||
174
src/app/state/state_cache.rs
Normal file
174
src/app/state/state_cache.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
use super::editor::TextEditor;
|
||||||
|
use crate::app::tab::{Tab, compute_content_hash};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct CachedTab {
|
||||||
|
pub diff: Option<String>,
|
||||||
|
pub full_content: Option<String>, // This is used for 'new files' that don't have a path
|
||||||
|
pub file_path: Option<PathBuf>,
|
||||||
|
pub is_modified: bool,
|
||||||
|
pub title: String,
|
||||||
|
pub original_content_hash: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct StateCache {
|
||||||
|
pub tabs: Vec<CachedTab>,
|
||||||
|
pub active_tab_index: usize,
|
||||||
|
pub tab_counter: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Tab> for CachedTab {
|
||||||
|
fn from(tab: &Tab) -> Self {
|
||||||
|
if let Some(file_path) = &tab.file_path {
|
||||||
|
let original_content = std::fs::read_to_string(file_path).unwrap_or_default();
|
||||||
|
let diff = if tab.is_modified {
|
||||||
|
Some(diffy::create_patch(&original_content, &tab.content).to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
diff,
|
||||||
|
full_content: None,
|
||||||
|
file_path: tab.file_path.clone(),
|
||||||
|
is_modified: tab.is_modified,
|
||||||
|
title: tab.title.clone(),
|
||||||
|
original_content_hash: tab.original_content_hash,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self {
|
||||||
|
diff: None,
|
||||||
|
full_content: Some(tab.content.clone()),
|
||||||
|
file_path: None,
|
||||||
|
is_modified: tab.is_modified,
|
||||||
|
title: tab.title.clone(),
|
||||||
|
original_content_hash: tab.original_content_hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CachedTab> for Tab {
|
||||||
|
fn from(cached: CachedTab) -> Self {
|
||||||
|
if let Some(file_path) = cached.file_path {
|
||||||
|
let original_content = std::fs::read_to_string(&file_path).unwrap_or_default();
|
||||||
|
let current_content = if let Some(diff_str) = cached.diff {
|
||||||
|
match diffy::Patch::from_str(&diff_str) {
|
||||||
|
Ok(patch) => {
|
||||||
|
match diffy::apply(&original_content, &patch) {
|
||||||
|
Ok(content) => content,
|
||||||
|
Err(_) => {
|
||||||
|
eprintln!("Warning: Failed to apply diff for {}, using original content",
|
||||||
|
file_path.display());
|
||||||
|
original_content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
eprintln!("Warning: Failed to parse diff for {}, using original content",
|
||||||
|
file_path.display());
|
||||||
|
original_content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
original_content
|
||||||
|
};
|
||||||
|
|
||||||
|
let original_hash = compute_content_hash(&std::fs::read_to_string(&file_path).unwrap_or_default());
|
||||||
|
let expected_hash = cached.original_content_hash;
|
||||||
|
|
||||||
|
let mut tab = Tab::new_with_file(current_content, file_path);
|
||||||
|
tab.title = cached.title;
|
||||||
|
|
||||||
|
if original_hash != expected_hash {
|
||||||
|
tab.is_modified = true;
|
||||||
|
} else {
|
||||||
|
tab.is_modified = cached.is_modified;
|
||||||
|
tab.original_content_hash = cached.original_content_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
tab
|
||||||
|
} else {
|
||||||
|
let content = cached.full_content.unwrap_or_default();
|
||||||
|
let mut tab = Tab::new_empty(1);
|
||||||
|
tab.content = content;
|
||||||
|
tab.title = cached.title;
|
||||||
|
tab.is_modified = cached.is_modified;
|
||||||
|
tab.original_content_hash = cached.original_content_hash;
|
||||||
|
tab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextEditor {
|
||||||
|
pub fn state_cache_path() -> Option<PathBuf> {
|
||||||
|
let cache_dir = if let Some(cache_dir) = dirs::cache_dir() {
|
||||||
|
cache_dir.join(env!("CARGO_PKG_NAME"))
|
||||||
|
} else if let Some(home_dir) = dirs::home_dir() {
|
||||||
|
home_dir.join(".cache").join(env!("CARGO_PKG_NAME"))
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(cache_dir.join("state.json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_state_cache(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if !self.state_cache {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let cache_path = Self::state_cache_path().ok_or("Cannot determine cache directory")?;
|
||||||
|
|
||||||
|
if !cache_path.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = std::fs::read_to_string(&cache_path)?;
|
||||||
|
let state_cache: StateCache = serde_json::from_str(&content)?;
|
||||||
|
|
||||||
|
if !state_cache.tabs.is_empty() {
|
||||||
|
self.tabs = state_cache.tabs.into_iter().map(Tab::from).collect();
|
||||||
|
self.active_tab_index = std::cmp::min(state_cache.active_tab_index, self.tabs.len() - 1);
|
||||||
|
self.tab_counter = state_cache.tab_counter;
|
||||||
|
self.text_needs_processing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_state_cache(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if !self.state_cache {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let cache_path = Self::state_cache_path().ok_or("Cannot determine cache directory")?;
|
||||||
|
|
||||||
|
if let Some(parent) = cache_path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state_cache = StateCache {
|
||||||
|
tabs: self.tabs.iter().map(CachedTab::from).collect(),
|
||||||
|
active_tab_index: self.active_tab_index,
|
||||||
|
tab_counter: self.tab_counter,
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = serde_json::to_string_pretty(&state_cache)?;
|
||||||
|
std::fs::write(&cache_path, content)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_state_cache() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if let Some(cache_path) = Self::state_cache_path() {
|
||||||
|
if cache_path.exists() {
|
||||||
|
std::fs::remove_file(cache_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,11 @@ impl TextEditor {
|
|||||||
self.update_find_matches();
|
self.update_find_matches();
|
||||||
}
|
}
|
||||||
self.text_needs_processing = true;
|
self.text_needs_processing = true;
|
||||||
|
|
||||||
|
// Save state cache after adding new tab
|
||||||
|
if let Err(e) = self.save_state_cache() {
|
||||||
|
eprintln!("Failed to save state cache: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_tab(&mut self, tab_index: usize) {
|
pub fn close_tab(&mut self, tab_index: usize) {
|
||||||
@ -32,6 +37,11 @@ impl TextEditor {
|
|||||||
self.update_find_matches();
|
self.update_find_matches();
|
||||||
}
|
}
|
||||||
self.text_needs_processing = true;
|
self.text_needs_processing = true;
|
||||||
|
|
||||||
|
// Save state cache after closing tab
|
||||||
|
if let Err(e) = self.save_state_cache() {
|
||||||
|
eprintln!("Failed to save state cache: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +52,11 @@ impl TextEditor {
|
|||||||
self.update_find_matches();
|
self.update_find_matches();
|
||||||
}
|
}
|
||||||
self.text_needs_processing = true;
|
self.text_needs_processing = true;
|
||||||
|
|
||||||
|
// Save state cache after switching tabs
|
||||||
|
if let Err(e) = self.save_state_cache() {
|
||||||
|
eprintln!("Failed to save state cache: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,7 @@ use std::path::PathBuf;
|
|||||||
pub fn compute_content_hash(content: &str) -> u64 {
|
pub fn compute_content_hash(content: &str) -> u64 {
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
content.hash(&mut hasher);
|
content.hash(&mut hasher);
|
||||||
let hash = hasher.finish();
|
hasher.finish()
|
||||||
hash
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@ -43,6 +43,10 @@ pub(crate) fn open_file(app: &mut TextEditor) {
|
|||||||
if app.show_find && !app.find_query.is_empty() {
|
if app.show_find && !app.find_query.is_empty() {
|
||||||
app.update_find_matches();
|
app.update_find_matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Err(e) = app.save_state_cache() {
|
||||||
|
eprintln!("Failed to save state cache: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Failed to open file: {err}");
|
eprintln!("Failed to open file: {err}");
|
||||||
@ -81,6 +85,10 @@ pub(crate) fn save_to_path(app: &mut TextEditor, path: PathBuf) {
|
|||||||
active_tab.file_path = Some(path.to_path_buf());
|
active_tab.file_path = Some(path.to_path_buf());
|
||||||
active_tab.title = title.to_string();
|
active_tab.title = title.to_string();
|
||||||
active_tab.mark_as_saved();
|
active_tab.mark_as_saved();
|
||||||
|
|
||||||
|
if let Err(e) = app.save_state_cache() {
|
||||||
|
eprintln!("Failed to save state cache: {e}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Failed to save file: {err}");
|
eprintln!("Failed to save file: {err}");
|
||||||
|
|||||||
@ -158,6 +158,13 @@ pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) -> egui::R
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Save state cache when content changes (after releasing the borrow)
|
||||||
|
if content_changed {
|
||||||
|
if let Err(e) = app.save_state_cache() {
|
||||||
|
eprintln!("Failed to save state cache: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if content_changed && app.show_find && !app.find_query.is_empty() {
|
if content_changed && app.show_find && !app.find_query.is_empty() {
|
||||||
app.update_find_matches();
|
app.update_find_matches();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,75 @@ pub(crate) fn preferences_window(app: &mut TextEditor, ctx: &egui::Context) {
|
|||||||
})
|
})
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
|
|
||||||
|
ui.heading("General Settings");
|
||||||
|
ui.add_space(8.0);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui
|
||||||
|
.checkbox(&mut app.state_cache, "State Cache")
|
||||||
|
.on_hover_text(
|
||||||
|
"Save and restore open tabs and unsaved changes between sessions"
|
||||||
|
)
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
app.save_config();
|
||||||
|
if !app.state_cache {
|
||||||
|
if let Err(e) = TextEditor::clear_state_cache() {
|
||||||
|
eprintln!("Failed to clear state cache: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(4.0);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui
|
||||||
|
.checkbox(&mut app.show_line_numbers, "Show Line Numbers")
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
app.save_config();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(4.0);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui
|
||||||
|
.checkbox(&mut app.word_wrap, "Word Wrap")
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
app.save_config();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(4.0);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui
|
||||||
|
.checkbox(&mut app.auto_hide_toolbar, "Auto Hide Toolbar")
|
||||||
|
.on_hover_text("Hide the menu bar until you move your mouse to the top")
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
app.save_config();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(4.0);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui
|
||||||
|
.checkbox(&mut app.hide_tab_bar, "Hide Tab Bar")
|
||||||
|
.on_hover_text("Hide the tab bar and show tab title in menu bar instead")
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
app.save_config();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(12.0);
|
||||||
|
ui.separator();
|
||||||
ui.heading("Font Settings");
|
ui.heading("Font Settings");
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
@ -60,7 +129,7 @@ pub(crate) fn preferences_window(app: &mut TextEditor, ctx: &egui::Context) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
app.apply_font_settings_with_ui(ctx, ui);
|
app.apply_font_settings(ctx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -99,17 +168,15 @@ pub(crate) fn preferences_window(app: &mut TextEditor, ctx: &egui::Context) {
|
|||||||
let clamped_size = new_size.clamp(8.0, 32.0);
|
let clamped_size = new_size.clamp(8.0, 32.0);
|
||||||
if (app.font_size - clamped_size).abs() > 0.1 {
|
if (app.font_size - clamped_size).abs() > 0.1 {
|
||||||
app.font_size = clamped_size;
|
app.font_size = clamped_size;
|
||||||
app.apply_font_settings_with_ui(ctx, ui);
|
app.apply_font_settings(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.font_size_input = None;
|
app.font_size_input = None;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(12.0);
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
ui.label("Preview:");
|
ui.label("Preview:");
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user