Compare commits

...

4 Commits

8 changed files with 118 additions and 146 deletions

View File

@ -21,6 +21,8 @@ pub struct Config {
pub font_family: String, pub font_family: String,
#[serde(default = "default_font_size")] #[serde(default = "default_font_size")]
pub font_size: f32, pub font_size: f32,
#[serde(default = "default_syntax_highlighting")]
pub syntax_highlighting: bool,
// pub vim_mode: bool, // pub vim_mode: bool,
} }
@ -45,18 +47,22 @@ fn default_font_family() -> String {
fn default_font_size() -> f32 { fn default_font_size() -> f32 {
14.0 14.0
} }
fn default_syntax_highlighting() -> bool {
false
}
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
auto_hide_toolbar: false, auto_hide_toolbar: default_auto_hide_toolbar(),
hide_tab_bar: true, hide_tab_bar: default_hide_tab_bar(),
show_line_numbers: false, show_line_numbers: default_show_line_numbers(),
word_wrap: true, word_wrap: default_word_wrap(),
theme: Theme::default(), theme: Theme::default(),
line_side: false, line_side: default_line_side(),
font_family: "Proportional".to_string(), font_family: default_font_family(),
font_size: 14.0, font_size: default_font_size(),
syntax_highlighting: default_syntax_highlighting(),
// vim_mode: false, // vim_mode: false,
} }
} }

View File

@ -1,21 +1,10 @@
use super::editor::TextEditor; use super::editor::TextEditor;
use crate::app::config::Config; use crate::app::config::Config;
use crate::app::tab::Tab;
use crate::app::theme; use crate::app::theme;
impl TextEditor { impl TextEditor {
pub fn from_config(config: Config) -> Self { pub fn from_config(config: Config) -> Self {
Self { Self {
tabs: vec![Tab::new_empty(1)],
active_tab_index: 0,
tab_counter: 1,
show_about: false,
show_shortcuts: false,
show_find: false,
show_preferences: false,
pending_unsaved_action: None,
force_quit_confirmed: false,
clean_quit_requested: false,
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,
@ -24,30 +13,8 @@ impl TextEditor {
line_side: config.line_side, line_side: config.line_side,
font_family: config.font_family, font_family: config.font_family,
font_size: config.font_size, font_size: config.font_size,
font_size_input: None, syntax_highlighting: config.syntax_highlighting,
zoom_factor: 1.0, ..Default::default()
menu_interaction_active: false,
tab_bar_rect: None,
menu_bar_stable_until: None,
text_processing_result: std::sync::Arc::new(std::sync::Mutex::new(Default::default())),
_processing_thread_handle: None,
find_query: String::new(),
replace_query: String::new(),
find_matches: Vec::new(),
current_match_index: None,
case_sensitive_search: false,
show_replace_section: false,
prev_show_find: false,
focus_find: false,
// vim_mode: config.vim_mode,
previous_cursor_position: None,
previous_content: String::new(),
previous_cursor_char_index: None,
current_cursor_line: 0,
previous_cursor_line: 0,
font_settings_changed: false,
text_needs_processing: false,
should_select_current_match: false,
} }
} }
@ -87,6 +54,7 @@ impl TextEditor {
line_side: self.line_side, line_side: self.line_side,
font_family: self.font_family.to_string(), font_family: self.font_family.to_string(),
font_size: self.font_size, font_size: self.font_size,
syntax_highlighting: self.syntax_highlighting,
// vim_mode: self.vim_mode, // vim_mode: self.vim_mode,
} }
} }

View File

@ -20,6 +20,7 @@ impl Default for TextEditor {
word_wrap: true, word_wrap: true,
auto_hide_toolbar: false, auto_hide_toolbar: false,
hide_tab_bar: true, hide_tab_bar: true,
syntax_highlighting: false,
theme: Theme::default(), theme: Theme::default(),
line_side: false, line_side: false,
font_family: "Proportional".to_string(), font_family: "Proportional".to_string(),

View File

@ -45,6 +45,7 @@ pub struct TextEditor {
pub(crate) word_wrap: bool, pub(crate) word_wrap: bool,
pub(crate) auto_hide_toolbar: bool, pub(crate) auto_hide_toolbar: bool,
pub(crate) hide_tab_bar: bool, pub(crate) hide_tab_bar: bool,
pub(crate) syntax_highlighting: bool,
pub(crate) theme: Theme, pub(crate) theme: Theme,
pub(crate) line_side: bool, pub(crate) line_side: bool,
pub(crate) font_family: String, pub(crate) font_family: String,

View File

@ -2,35 +2,32 @@ use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::path::PathBuf; use std::path::PathBuf;
pub fn compute_content_hash(content: &str, hasher: &mut DefaultHasher) -> u64 { pub fn compute_content_hash(content: &str) -> u64 {
content.hash(hasher); let mut hasher = DefaultHasher::new();
hasher.finish() content.hash(&mut hasher);
let hash = hasher.finish();
hash
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Tab { pub struct Tab {
pub content: String, pub content: String,
pub original_content_hash: u64, pub original_content_hash: u64,
pub last_content_hash: u64,
pub file_path: Option<PathBuf>, pub file_path: Option<PathBuf>,
pub is_modified: bool, pub is_modified: bool,
pub title: String, pub title: String,
pub hasher: DefaultHasher,
} }
impl Tab { impl Tab {
pub fn new_empty(tab_number: usize) -> Self { pub fn new_empty(tab_number: usize) -> Self {
let content = String::new(); let content = String::new();
let mut hasher = DefaultHasher::new(); let hash = compute_content_hash(&content);
let hash = compute_content_hash(&content, &mut hasher);
Self { Self {
original_content_hash: hash, original_content_hash: hash,
last_content_hash: hash,
content, content,
file_path: None, file_path: None,
is_modified: false, is_modified: false,
title: format!("new_{tab_number}"), title: format!("new_{tab_number}"),
hasher,
} }
} }
@ -38,19 +35,16 @@ impl Tab {
let title = file_path let title = file_path
.file_name() .file_name()
.and_then(|n| n.to_str()) .and_then(|n| n.to_str())
.unwrap_or("Untitled") .unwrap_or("UNKNOWN")
.to_string(); .to_string();
let mut hasher = DefaultHasher::new(); let hash = compute_content_hash(&content);
let hash = compute_content_hash(&content, &mut hasher);
Self { Self {
original_content_hash: hash, original_content_hash: hash,
last_content_hash: hash,
content, content,
file_path: Some(file_path), file_path: Some(file_path),
is_modified: false, is_modified: false,
title, title,
hasher,
} }
} }
@ -63,15 +57,13 @@ impl Tab {
if self.title.starts_with("new_") { if self.title.starts_with("new_") {
self.is_modified = !self.content.is_empty(); self.is_modified = !self.content.is_empty();
} else { } else {
let current_hash = compute_content_hash(&self.content, &mut self.hasher); let current_hash = compute_content_hash(&self.content);
self.is_modified = current_hash != self.last_content_hash; self.is_modified = current_hash != self.original_content_hash;
self.last_content_hash = current_hash;
} }
} }
pub fn mark_as_saved(&mut self) { pub fn mark_as_saved(&mut self) {
self.original_content_hash = compute_content_hash(&self.content, &mut self.hasher); self.original_content_hash = compute_content_hash(&self.content);
self.last_content_hash = self.original_content_hash;
self.is_modified = false; self.is_modified = false;
} }
} }

View File

@ -80,6 +80,8 @@ pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) -> egui::R
let show_shortcuts = app.show_shortcuts; let show_shortcuts = app.show_shortcuts;
let word_wrap = app.word_wrap; let word_wrap = app.word_wrap;
let font_size = app.font_size; let font_size = app.font_size;
let font_id = app.get_font_id();
let syntax_highlighting_enabled = app.syntax_highlighting;
let reset_zoom_key = egui::Id::new("editor_reset_zoom"); let reset_zoom_key = egui::Id::new("editor_reset_zoom");
let should_reset_zoom = ui let should_reset_zoom = ui
@ -164,26 +166,29 @@ pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) -> egui::R
f32::INFINITY f32::INFINITY
}; };
// Determine the language for syntax highlighting
let language = get_language_from_extension(active_tab.file_path.as_deref()); let language = get_language_from_extension(active_tab.file_path.as_deref());
// Create a code theme based on the current system theme visuals
let theme = create_code_theme_from_visuals(ui.visuals(), font_size); let theme = create_code_theme_from_visuals(ui.visuals(), font_size);
let mut layouter = |ui: &egui::Ui, string: &dyn egui::TextBuffer, wrap_width: f32| { let mut layouter = |ui: &egui::Ui, string: &dyn egui::TextBuffer, wrap_width: f32| {
let text = string.as_str(); let text = string.as_str();
let mut layout_job = if language == "txt" { let mut layout_job = if syntax_highlighting_enabled && language != "txt" {
syntax_highlighting::highlight(ui.ctx(), &ui.style().clone(), &theme, text, "")
} else {
syntax_highlighting::highlight(ui.ctx(), &ui.style().clone(), &theme, text, &language) syntax_highlighting::highlight(ui.ctx(), &ui.style().clone(), &theme, text, &language)
} else {
syntax_highlighting::highlight(ui.ctx(), &ui.style().clone(), &theme, text, "")
}; };
if syntax_highlighting_enabled && language != "txt" {
for section in &mut layout_job.sections {
section.format.font_id = font_id.clone();
}
}
layout_job.wrap.max_width = wrap_width; layout_job.wrap.max_width = wrap_width;
ui.fonts(|f| f.layout_job(layout_job)) ui.fonts(|f| f.layout_job(layout_job))
}; };
let text_edit = egui::TextEdit::multiline(&mut active_tab.content) let text_edit = egui::TextEdit::multiline(&mut active_tab.content)
.frame(false) .frame(false)
.font(egui::TextStyle::Monospace)
.code_editor() .code_editor()
.desired_width(desired_width) .desired_width(desired_width)
.desired_rows(0) .desired_rows(0)
@ -248,11 +253,6 @@ pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) -> egui::R
app.previous_content = content.to_owned(); app.previous_content = content.to_owned();
app.previous_cursor_char_index = current_cursor_pos; app.previous_cursor_char_index = current_cursor_pos;
if let Some(active_tab) = app.get_active_tab_mut() {
active_tab.last_content_hash =
crate::app::tab::compute_content_hash(&active_tab.content, &mut active_tab.hasher);
}
} }
if app.font_settings_changed || app.text_needs_processing { if app.font_settings_changed || app.text_needs_processing {

View File

@ -185,6 +185,10 @@ pub(crate) fn menu_bar(app: &mut TextEditor, ctx: &egui::Context) {
app.save_config(); app.save_config();
ui.close_kind(UiKind::Menu); ui.close_kind(UiKind::Menu);
} }
if ui.checkbox(&mut app.syntax_highlighting, "Syntax Highlighting").clicked() {
app.save_config();
ui.close_kind(UiKind::Menu);
}
if ui.checkbox(&mut app.word_wrap, "Word Wrap").clicked() { if ui.checkbox(&mut app.word_wrap, "Word Wrap").clicked() {
app.save_config(); app.save_config();
ui.close_kind(UiKind::Menu); ui.close_kind(UiKind::Menu);
@ -276,21 +280,15 @@ pub(crate) fn menu_bar(app: &mut TextEditor, ctx: &egui::Context) {
if app.hide_tab_bar { if app.hide_tab_bar {
let tab_title = if let Some(tab) = app.get_active_tab() { let tab_title = if let Some(tab) = app.get_active_tab() {
tab.title.to_owned() tab.get_display_title()
} else { } else {
let empty_tab = crate::app::tab::Tab::new_empty(1); let empty_tab = crate::app::tab::Tab::new_empty(1);
empty_tab.title.to_owned() empty_tab.get_display_title()
}; };
let window_width = ctx.screen_rect().width(); let window_width = ctx.screen_rect().width();
let font_id = ui.style().text_styles[&egui::TextStyle::Body].to_owned(); let font_id = ui.style().text_styles[&egui::TextStyle::Body].to_owned();
let tab_title = if app.get_active_tab().is_some_and(|tab| tab.is_modified) {
format!("{tab_title}*")
} else {
tab_title
};
let text_galley = ui.fonts(|fonts| { let text_galley = ui.fonts(|fonts| {
fonts.layout_job(egui::text::LayoutJob::simple_singleline( fonts.layout_job(egui::text::LayoutJob::simple_singleline(
tab_title, tab_title,

View File

@ -3,9 +3,14 @@ use eframe::egui::{self, Frame};
pub(crate) fn tab_bar(app: &mut TextEditor, ctx: &egui::Context) { pub(crate) fn tab_bar(app: &mut TextEditor, ctx: &egui::Context) {
let frame = Frame::NONE.fill(ctx.style().visuals.panel_fill); let frame = Frame::NONE.fill(ctx.style().visuals.panel_fill);
let response = egui::TopBottomPanel::top("tab_bar") let tab_bar = egui::TopBottomPanel::top("tab_bar")
.frame(frame) .frame(frame)
.show(ctx, |ui| { .show(ctx, |ui| {
egui::ScrollArea::horizontal()
.auto_shrink([false, true])
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
.scroll_source(egui::scroll_area::ScrollSource::DRAG)
.show(ui, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
let mut tab_to_close_unmodified = None; let mut tab_to_close_unmodified = None;
let mut tab_to_close_modified = None; let mut tab_to_close_modified = None;
@ -34,8 +39,7 @@ pub(crate) fn tab_bar(app: &mut TextEditor, ctx: &egui::Context) {
label_text = label_text.italics(); label_text = label_text.italics();
} }
let tab_response = let tab_response = ui.add(egui::Label::new(label_text).selectable(false).sense(egui::Sense::click()));
ui.add(egui::Label::new(label_text).sense(egui::Sense::click()));
if tab_response.clicked() { if tab_response.clicked() {
tab_to_switch = Some(i); tab_to_switch = Some(i);
} }
@ -83,6 +87,8 @@ pub(crate) fn tab_bar(app: &mut TextEditor, ctx: &egui::Context) {
} }
}); });
}); });
});
app.tab_bar_rect = Some(tab_bar.response.rect);
app.tab_bar_rect = Some(response.response.rect);
} }