Compare commits
No commits in common. "5dc0b6d638915a7ae09da384da66bd7f18c98787" and "fd26344b5fb96dc757c3dc7aa523d2151b6d1e0e" have entirely different histories.
5dc0b6d638
...
fd26344b5f
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ced"
|
name = "ced"
|
||||||
version = "0.1.3"
|
version = "0.0.9"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -8,12 +8,7 @@ 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"
|
|
||||||
plist = "1.7.4"
|
|
||||||
diffy = "0.4.2"
|
|
||||||
uuid = { version = "1.0", features = ["v4"] }
|
|
||||||
|
|||||||
10
README.md
10
README.md
@ -9,7 +9,7 @@ There is a disturbing lack of simple GUI text editors available on Linux nativel
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Sane text editing with standard keybindings (`Ctrl+A`, `Ctrl+C`, etc.).
|
* Sane text editing with standard keybindings (`Ctrl+A`, `Ctrl+C`, etc.).
|
||||||
* Choose between opening fresh every time, like Notepad, or maintaining a consistent state like Notepad++.
|
* Opens with a blank slate for quick typing, remember Notepad?
|
||||||
* Separate UI zoom that doesn't affect font size (`Ctrl+Shift` + `+`/`-`).
|
* Separate UI zoom that doesn't affect font size (`Ctrl+Shift` + `+`/`-`).
|
||||||
* Ricers rejoice, your `pywal` colors will be used!
|
* Ricers rejoice, your `pywal` colors will be used!
|
||||||
* Weirdly smooth typing experience.
|
* Weirdly smooth typing experience.
|
||||||
@ -39,7 +39,6 @@ sudo install -Dm644 ced.desktop /usr/share/applications/ced.desktop
|
|||||||
Here is an example `config.toml`:
|
Here is an example `config.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
state_cache = true
|
|
||||||
auto_hide_toolbar = false
|
auto_hide_toolbar = false
|
||||||
show_line_numbers = false
|
show_line_numbers = false
|
||||||
word_wrap = false
|
word_wrap = false
|
||||||
@ -47,18 +46,15 @@ theme = "System"
|
|||||||
line_side = false
|
line_side = false
|
||||||
font_family = "Monospace"
|
font_family = "Monospace"
|
||||||
font_size = 16.0
|
font_size = 16.0
|
||||||
syntax_highlighting = true
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
|--------|---------|-------------|
|
|--------|---------|-------------|
|
||||||
| `state_cache` | `false` | If `true`, open files will have their unsaved changes cached and will be automatically opened when starting a new session. |
|
|
||||||
| `auto_hide_toolbar` | `false` | If `true`, the menu bar at the top will be hidden. Move your mouse to the top of the window to reveal it. |
|
| `auto_hide_toolbar` | `false` | If `true`, the menu bar at the top will be hidden. Move your mouse to the top of the window to reveal it. |
|
||||||
| `hide_tab_bar` | 'true' | If `false`, a separate tab bar will be drawn below the toolbar. |
|
| `hide_tab_bar` | 'true' | If `false`, a separate tab bar will be drawn below the toolbar. |
|
||||||
| `show_line_numbers` | `false` | If `true`, line numbers will be displayed on the side specified by `line_side`. |
|
| `show_line_numbers` | `false` | If `true`, line numbers will be displayed on the side specified by `line_side`. |
|
||||||
| `syntax_highlighting` | `false` | If `true`, text will be highlighted based on detected language. |
|
|
||||||
| `line_side` | `false` | If `false`, line numbers are on the left. If `true`, they are on the right. |
|
| `line_side` | `false` | If `false`, line numbers are on the left. If `true`, they are on the right. |
|
||||||
| `word_wrap` | `false` | If `true`, lines will wrap when they reach the edge of the window. |
|
| `word_wrap` | `false` | If `true`, lines will wrap when they reach the edge of the window. |
|
||||||
| `font_family` | `"Proportional"` | The font family used for the editor text. |
|
| `font_family` | `"Proportional"` | The font family used for the editor text. |
|
||||||
@ -69,7 +65,9 @@ syntax_highlighting = true
|
|||||||
In order of importance.
|
In order of importance.
|
||||||
| Feature | Info |
|
| Feature | Info |
|
||||||
| ------- | ---- |
|
| ------- | ---- |
|
||||||
| **LSP:** | Looking at allowing you to use/attach your own tools for this. |
|
| **Find/Replace:** | Functioning. |
|
||||||
|
| **State/Cache:** | A toggleable option to keep an application state and prevent "Quit without saving" warnings. |
|
||||||
|
| **Syntax Highlighting/LSP:** | Looking at allowing you to use/attach your own tools for this. |
|
||||||
| **Choose Font** | More than just Monospace/Proportional. |
|
| **Choose Font** | More than just Monospace/Proportional. |
|
||||||
| **Vim Mode:** | It's in-escapable. |
|
| **Vim Mode:** | It's in-escapable. |
|
||||||
| **CLI Mode:** | 💀 |
|
| **CLI Mode:** | 💀 |
|
||||||
|
|||||||
@ -5,8 +5,6 @@ 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")]
|
||||||
@ -28,10 +26,6 @@ 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
|
||||||
}
|
}
|
||||||
@ -60,7 +54,6 @@ 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,7 +5,6 @@ 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,7 +5,6 @@ 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,
|
||||||
@ -21,11 +20,6 @@ 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);
|
||||||
|
|
||||||
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);
|
||||||
@ -52,7 +46,6 @@ 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,7 +8,6 @@ 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,7 +34,6 @@ 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,
|
||||||
|
|||||||
@ -30,6 +30,7 @@ impl TextEditor {
|
|||||||
let search_slice = if search_content.is_char_boundary(start) {
|
let search_slice = if search_content.is_char_boundary(start) {
|
||||||
&search_content[start..]
|
&search_content[start..]
|
||||||
} else {
|
} else {
|
||||||
|
// Find next valid boundary
|
||||||
while start < search_content.len() && !search_content.is_char_boundary(start) {
|
while start < search_content.len() && !search_content.is_char_boundary(start) {
|
||||||
start += 1;
|
start += 1;
|
||||||
}
|
}
|
||||||
@ -44,6 +45,7 @@ impl TextEditor {
|
|||||||
self.find_matches
|
self.find_matches
|
||||||
.push((absolute_pos, absolute_pos + query.len()));
|
.push((absolute_pos, absolute_pos + query.len()));
|
||||||
|
|
||||||
|
// Advance to next valid character boundary instead of just +1
|
||||||
start = absolute_pos + 1;
|
start = absolute_pos + 1;
|
||||||
while start < search_content.len() && !search_content.is_char_boundary(start) {
|
while start < search_content.len() && !search_content.is_char_boundary(start) {
|
||||||
start += 1;
|
start += 1;
|
||||||
|
|||||||
@ -15,22 +15,16 @@ 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() && !self.state_cache {
|
if self.has_unsaved_changes() {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,40 +65,49 @@ 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_centered(|ui| {
|
ui.vertical(|ui| {
|
||||||
ui.add_space(8.0);
|
|
||||||
ui.label(egui::RichText::new(&confirmation_text).size(14.0));
|
ui.label(egui::RichText::new(&confirmation_text).size(14.0));
|
||||||
ui.add_space(4.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
for file in &files_to_list {
|
for file in &files_to_list {
|
||||||
ui.label(egui::RichText::new(file).size(12.0).color(error_color));
|
ui.label(egui::RichText::new(format!("• {file}")).size(18.0).weak());
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
if ui.button("Cancel").clicked() {
|
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() {
|
||||||
cancel_action = true;
|
cancel_action = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
if ui
|
let destructive_color = ui.visuals().error_fg_color;
|
||||||
.button(egui::RichText::new(&button_text).color(error_color))
|
let confirm_button = egui::Button::new(&button_text)
|
||||||
.clicked()
|
.fill(destructive_color)
|
||||||
{
|
.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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -114,17 +117,9 @@ impl TextEditor {
|
|||||||
|
|
||||||
if let Some(action) = close_action_now {
|
if let Some(action) = close_action_now {
|
||||||
match action {
|
match action {
|
||||||
UnsavedAction::Quit => {
|
UnsavedAction::Quit => self.force_quit(ctx),
|
||||||
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;
|
||||||
|
|||||||
@ -1,248 +0,0 @@
|
|||||||
use super::editor::TextEditor;
|
|
||||||
use crate::app::tab::{Tab, compute_content_hash};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct CachedTab {
|
|
||||||
pub diff_file: Option<PathBuf>, // Path to diff file for modified tabs
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_diff_file(diff_content: &str) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
|
||||||
let diffs_dir = TextEditor::diffs_cache_dir().ok_or("Cannot determine cache directory")?;
|
|
||||||
std::fs::create_dir_all(&diffs_dir)?;
|
|
||||||
|
|
||||||
let diff_filename = format!("{}.diff", Uuid::new_v4());
|
|
||||||
let diff_path = diffs_dir.join(diff_filename);
|
|
||||||
|
|
||||||
std::fs::write(&diff_path, diff_content)?;
|
|
||||||
Ok(diff_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_diff_file(diff_path: &PathBuf) -> Result<String, Box<dyn std::error::Error>> {
|
|
||||||
Ok(std::fs::read_to_string(diff_path)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
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_file = if tab.is_modified {
|
|
||||||
let diff_content = diffy::create_patch(&original_content, &tab.content);
|
|
||||||
match create_diff_file(&diff_content.to_string()) {
|
|
||||||
Ok(path) => Some(path),
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Warning: Failed to create diff file: {}", e);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
diff_file,
|
|
||||||
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_file: 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_path) = cached.diff_file {
|
|
||||||
match load_diff_file(&diff_path) {
|
|
||||||
Ok(diff_content) => {
|
|
||||||
match diffy::Patch::from_str(&diff_content) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Warning: Failed to load diff file {:?}: {}, using original content",
|
|
||||||
diff_path, e);
|
|
||||||
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 diffs_cache_dir() -> 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("diffs"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cleanup_orphaned_diffs(active_diff_files: &[PathBuf]) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
if let Some(diffs_dir) = Self::diffs_cache_dir() {
|
|
||||||
if diffs_dir.exists() {
|
|
||||||
for entry in std::fs::read_dir(diffs_dir)? {
|
|
||||||
let entry = entry?;
|
|
||||||
let path = entry.path();
|
|
||||||
if path.extension().and_then(|s| s.to_str()) == Some("diff") {
|
|
||||||
if !active_diff_files.contains(&path) {
|
|
||||||
let _ = std::fs::remove_file(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
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 active_diff_files: Vec<PathBuf> = state_cache.tabs
|
|
||||||
.iter()
|
|
||||||
.filter_map(|tab| tab.diff_file.clone())
|
|
||||||
.collect();
|
|
||||||
let _ = Self::cleanup_orphaned_diffs(&active_diff_files);
|
|
||||||
|
|
||||||
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)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(diffs_dir) = Self::diffs_cache_dir() {
|
|
||||||
if diffs_dir.exists() {
|
|
||||||
let _ = std::fs::remove_dir_all(diffs_dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -18,10 +18,6 @@ impl TextEditor {
|
|||||||
self.update_find_matches();
|
self.update_find_matches();
|
||||||
}
|
}
|
||||||
self.text_needs_processing = true;
|
self.text_needs_processing = true;
|
||||||
|
|
||||||
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) {
|
||||||
@ -36,10 +32,6 @@ impl TextEditor {
|
|||||||
self.update_find_matches();
|
self.update_find_matches();
|
||||||
}
|
}
|
||||||
self.text_needs_processing = true;
|
self.text_needs_processing = true;
|
||||||
|
|
||||||
if let Err(e) = self.save_state_cache() {
|
|
||||||
eprintln!("Failed to save state cache: {e}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,10 +42,6 @@ impl TextEditor {
|
|||||||
self.update_find_matches();
|
self.update_find_matches();
|
||||||
}
|
}
|
||||||
self.text_needs_processing = true;
|
self.text_needs_processing = true;
|
||||||
|
|
||||||
if let Err(e) = self.save_state_cache() {
|
|
||||||
eprintln!("Failed to save state cache: {e}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ impl TextEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the configured font ID based on the editor's font settings
|
||||||
pub fn get_font_id(&self) -> egui::FontId {
|
pub fn get_font_id(&self) -> egui::FontId {
|
||||||
let font_family = match self.font_family.as_str() {
|
let font_family = match self.font_family.as_str() {
|
||||||
"Monospace" => egui::FontFamily::Monospace,
|
"Monospace" => egui::FontFamily::Monospace,
|
||||||
@ -31,11 +32,13 @@ impl TextEditor {
|
|||||||
egui::FontId::new(self.font_size, font_family)
|
egui::FontId::new(self.font_size, font_family)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Immediately apply theme and save to configuration
|
||||||
pub fn set_theme(&mut self, ctx: &egui::Context) {
|
pub fn set_theme(&mut self, ctx: &egui::Context) {
|
||||||
theme::apply(self.theme, ctx);
|
theme::apply(self.theme, ctx);
|
||||||
self.save_config();
|
self.save_config();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply font settings with immediate text reprocessing
|
||||||
pub fn apply_font_settings(&mut self, ctx: &egui::Context) {
|
pub fn apply_font_settings(&mut self, ctx: &egui::Context) {
|
||||||
let font_family = match self.font_family.as_str() {
|
let font_family = match self.font_family.as_str() {
|
||||||
"Monospace" => egui::FontFamily::Monospace,
|
"Monospace" => egui::FontFamily::Monospace,
|
||||||
@ -53,18 +56,21 @@ impl TextEditor {
|
|||||||
self.save_config();
|
self.save_config();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply font settings with immediate text reprocessing
|
||||||
pub fn apply_font_settings_with_ui(&mut self, ctx: &egui::Context, ui: &egui::Ui) {
|
pub fn apply_font_settings_with_ui(&mut self, ctx: &egui::Context, ui: &egui::Ui) {
|
||||||
self.apply_font_settings(ctx);
|
self.apply_font_settings(ctx);
|
||||||
self.reprocess_text_for_font_change(ui);
|
self.reprocess_text_for_font_change(ui);
|
||||||
self.font_settings_changed = false;
|
self.font_settings_changed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trigger immediate text reprocessing when font settings change
|
||||||
pub fn reprocess_text_for_font_change(&mut self, ui: &egui::Ui) {
|
pub fn reprocess_text_for_font_change(&mut self, ui: &egui::Ui) {
|
||||||
if let Some(active_tab) = self.get_active_tab() {
|
if let Some(active_tab) = self.get_active_tab() {
|
||||||
self.process_text_for_rendering(&active_tab.content.to_string(), ui);
|
self.process_text_for_rendering(&active_tab.content.to_string(), ui);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculates the available width for the text editor, accounting for line numbers and separator
|
||||||
pub fn calculate_editor_dimensions(&self, ui: &egui::Ui) -> EditorDimensions {
|
pub fn calculate_editor_dimensions(&self, ui: &egui::Ui) -> EditorDimensions {
|
||||||
let total_available_width = ui.available_width();
|
let total_available_width = ui.available_width();
|
||||||
|
|
||||||
@ -108,6 +114,7 @@ impl TextEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculate the available width for non-word-wrapped content based on processed text data
|
||||||
pub fn calculate_content_based_width(&self, ui: &egui::Ui) -> f32 {
|
pub fn calculate_content_based_width(&self, ui: &egui::Ui) -> f32 {
|
||||||
let processing_result = self.get_text_processing_result();
|
let processing_result = self.get_text_processing_result();
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,8 @@ 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);
|
||||||
hasher.finish()
|
let hash = hasher.finish();
|
||||||
|
hash
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
105
src/app/theme.rs
105
src/app/theme.rs
@ -1,7 +1,5 @@
|
|||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use plist::{Dictionary, Value};
|
use egui_extras::syntax_highlighting::CodeTheme;
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use syntect::highlighting::{Theme as SyntectTheme, ThemeSet, ThemeSettings, Color as SyntectColor, UnderlineOption};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, Default)]
|
||||||
pub enum Theme {
|
pub enum Theme {
|
||||||
@ -198,102 +196,11 @@ fn detect_system_dark_mode() -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn egui_color_to_syntect(color: egui::Color32) -> SyntectColor {
|
pub fn create_code_theme_from_visuals(visuals: &egui::Visuals, font_size: f32) -> CodeTheme {
|
||||||
SyntectColor {
|
if visuals.dark_mode {
|
||||||
r: color.r(),
|
CodeTheme::dark(font_size)
|
||||||
g: color.g(),
|
} else {
|
||||||
b: color.b(),
|
CodeTheme::light(font_size)
|
||||||
a: color.a(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_code_theme_from_visuals(visuals: &egui::Visuals, font_size: f32) -> ThemeSet {
|
|
||||||
let text_color = visuals.override_text_color.unwrap_or(visuals.text_color());
|
|
||||||
let bg_color = visuals.extreme_bg_color;
|
|
||||||
let selection_color = visuals.selection.bg_fill;
|
|
||||||
let comment_color = blend_colors(text_color, bg_color, 0.6);
|
|
||||||
let keyword_color = if visuals.dark_mode {
|
|
||||||
blend_colors(egui::Color32::from_rgb(100, 149, 237), text_color, 0.8) // CornflowerBlue-like
|
|
||||||
} else {
|
|
||||||
blend_colors(egui::Color32::from_rgb(0, 0, 139), text_color, 0.8) // DarkBlue-like
|
|
||||||
};
|
|
||||||
let string_color = if visuals.dark_mode {
|
|
||||||
blend_colors(egui::Color32::from_rgb(144, 238, 144), text_color, 0.8) // LightGreen-like
|
|
||||||
} else {
|
|
||||||
blend_colors(egui::Color32::from_rgb(0, 128, 0), text_color, 0.8) // Green-like
|
|
||||||
};
|
|
||||||
let number_color = if visuals.dark_mode {
|
|
||||||
blend_colors(egui::Color32::from_rgb(255, 165, 0), text_color, 0.8) // Orange-like
|
|
||||||
} else {
|
|
||||||
blend_colors(egui::Color32::from_rgb(165, 42, 42), text_color, 0.8) // Brown-like
|
|
||||||
};
|
|
||||||
let function_color = if visuals.dark_mode {
|
|
||||||
blend_colors(egui::Color32::from_rgb(255, 20, 147), text_color, 0.8) // DeepPink-like
|
|
||||||
} else {
|
|
||||||
blend_colors(egui::Color32::from_rgb(128, 0, 128), text_color, 0.8) // Purple-like
|
|
||||||
};
|
|
||||||
|
|
||||||
let plist_theme = build_custom_theme_plist("System", &format!("{:?}", bg_color), &format!("{:?}", text_color), &format!("{:?}", comment_color), &format!("{:?}", string_color), &format!("{:?}", keyword_color));
|
|
||||||
let file = std::fs::File::create("system.tmTheme").unwrap();
|
|
||||||
let writer = std::io::BufWriter::new(file);
|
|
||||||
|
|
||||||
let _ =plist::to_writer_xml(writer, &plist_theme);
|
|
||||||
|
|
||||||
let loaded_file = std::fs::File::open("system.tmTheme").unwrap();
|
|
||||||
let mut loaded_reader = std::io::BufReader::new(loaded_file);
|
|
||||||
let loaded_theme = ThemeSet::load_from_reader(&mut loaded_reader).unwrap();
|
|
||||||
let mut set = ThemeSet::new();
|
|
||||||
set.add_from_folder(".").unwrap();
|
|
||||||
return set;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_custom_theme_plist(
|
|
||||||
theme_name: &str,
|
|
||||||
background_color: &str,
|
|
||||||
foreground_color: &str,
|
|
||||||
comment_color: &str,
|
|
||||||
string_color: &str,
|
|
||||||
keyword_color: &str,
|
|
||||||
) -> Value {
|
|
||||||
let mut root_dict = Dictionary::new();
|
|
||||||
root_dict.insert("name".to_string(), Value::String(theme_name.to_string()));
|
|
||||||
|
|
||||||
let mut settings_array = Vec::new();
|
|
||||||
|
|
||||||
let mut global_settings_dict = Dictionary::new();
|
|
||||||
let mut inner_global_settings = Dictionary::new();
|
|
||||||
inner_global_settings.insert("background".to_string(), Value::String(background_color.to_string()));
|
|
||||||
inner_global_settings.insert("foreground".to_string(), Value::String(foreground_color.to_string()));
|
|
||||||
global_settings_dict.insert("settings".to_string(), Value::Dictionary(inner_global_settings));
|
|
||||||
settings_array.push(Value::Dictionary(global_settings_dict));
|
|
||||||
|
|
||||||
let mut comment_scope_dict = Dictionary::new();
|
|
||||||
comment_scope_dict.insert("name".to_string(), Value::String("Comment".to_string()));
|
|
||||||
comment_scope_dict.insert("scope".to_string(), Value::String("comment".to_string()));
|
|
||||||
let mut comment_settings = Dictionary::new();
|
|
||||||
comment_settings.insert("foreground".to_string(), Value::String(comment_color.to_string()));
|
|
||||||
comment_settings.insert("fontStyle".to_string(), Value::String("italic".to_string()));
|
|
||||||
comment_scope_dict.insert("settings".to_string(), Value::Dictionary(comment_settings));
|
|
||||||
settings_array.push(Value::Dictionary(comment_scope_dict));
|
|
||||||
|
|
||||||
let mut string_scope_dict = Dictionary::new();
|
|
||||||
string_scope_dict.insert("name".to_string(), Value::String("String".to_string()));
|
|
||||||
string_scope_dict.insert("scope".to_string(), Value::String("string".to_string()));
|
|
||||||
let mut string_settings = Dictionary::new();
|
|
||||||
string_settings.insert("foreground".to_string(), Value::String(string_color.to_string()));
|
|
||||||
string_scope_dict.insert("settings".to_string(), Value::Dictionary(string_settings));
|
|
||||||
settings_array.push(Value::Dictionary(string_scope_dict));
|
|
||||||
|
|
||||||
let mut keyword_scope_dict = Dictionary::new();
|
|
||||||
keyword_scope_dict.insert("name".to_string(), Value::String("Keyword".to_string()));
|
|
||||||
keyword_scope_dict.insert("scope".to_string(), Value::String("keyword".to_string()));
|
|
||||||
let mut keyword_settings = Dictionary::new();
|
|
||||||
keyword_settings.insert("foreground".to_string(), Value::String(keyword_color.to_string()));
|
|
||||||
keyword_scope_dict.insert("settings".to_string(), Value::Dictionary(keyword_settings));
|
|
||||||
settings_array.push(Value::Dictionary(keyword_scope_dict));
|
|
||||||
|
|
||||||
root_dict.insert("settings".to_string(), Value::Array(settings_array));
|
|
||||||
|
|
||||||
Value::Dictionary(root_dict)
|
|
||||||
}
|
|
||||||
@ -43,10 +43,6 @@ 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}");
|
||||||
@ -85,10 +81,6 @@ 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}");
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use egui_extras::syntax_highlighting::{self};
|
|||||||
|
|
||||||
use super::find_highlight;
|
use super::find_highlight;
|
||||||
|
|
||||||
|
|
||||||
pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) -> egui::Response {
|
pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) -> egui::Response {
|
||||||
let _current_match_position = app.get_current_match_position();
|
let _current_match_position = app.get_current_match_position();
|
||||||
let show_find = app.show_find;
|
let show_find = app.show_find;
|
||||||
@ -100,15 +101,11 @@ pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) -> egui::R
|
|||||||
};
|
};
|
||||||
|
|
||||||
let language = super::languages::get_language_from_extension(active_tab.file_path.as_deref());
|
let language = super::languages::get_language_from_extension(active_tab.file_path.as_deref());
|
||||||
|
let theme = crate::app::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 syntect_theme =
|
|
||||||
// crate::app::theme::create_code_theme_from_visuals(ui.visuals(), font_size);
|
|
||||||
let text = string.as_str();
|
let text = string.as_str();
|
||||||
let theme = egui_extras::syntax_highlighting::CodeTheme::dark(font_size);
|
|
||||||
let mut layout_job = if syntax_highlighting_enabled && language != "txt" {
|
let mut layout_job = if syntax_highlighting_enabled && language != "txt" {
|
||||||
// let mut settings = egui_extras::syntax_highlighting::SyntectSettings::default();
|
|
||||||
// settings.ts = syntect_theme;
|
|
||||||
// syntax_highlighting::highlight_with(ui.ctx(), &ui.style().clone(), &theme, text, &language, &settings)
|
|
||||||
syntax_highlighting::highlight(ui.ctx(), &ui.style().clone(), &theme, text, &language)
|
syntax_highlighting::highlight(ui.ctx(), &ui.style().clone(), &theme, text, &language)
|
||||||
} else {
|
} else {
|
||||||
syntax_highlighting::highlight(ui.ctx(), &ui.style().clone(), &theme, text, "")
|
syntax_highlighting::highlight(ui.ctx(), &ui.style().clone(), &theme, text, "")
|
||||||
@ -158,12 +155,6 @@ pub(super) fn editor_view_ui(ui: &mut egui::Ui, app: &mut TextEditor) -> egui::R
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
pub fn get_language_from_extension(file_path: Option<&std::path::Path>) -> String {
|
pub fn get_language_from_extension(file_path: Option<&std::path::Path>) -> String {
|
||||||
let default_lang = "txt".to_string();
|
if let Some(path) = file_path {
|
||||||
|
|
||||||
let path = match file_path {
|
|
||||||
Some(p) => p,
|
|
||||||
None => return default_lang,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
|
if let Some(extension) = path.extension().and_then(|ext| ext.to_str()) {
|
||||||
match extension.to_lowercase().as_str() {
|
match extension.to_lowercase().as_str() {
|
||||||
"rs" => "rs".to_string(),
|
"rs" => "rs".to_string(),
|
||||||
@ -39,17 +33,23 @@ pub fn get_language_from_extension(file_path: Option<&std::path::Path>) -> Strin
|
|||||||
"vim" => "vim".to_string(),
|
"vim" => "vim".to_string(),
|
||||||
"dockerfile" => "dockerfile".to_string(),
|
"dockerfile" => "dockerfile".to_string(),
|
||||||
"makefile" => "makefile".to_string(),
|
"makefile" => "makefile".to_string(),
|
||||||
_ => default_lang,
|
_ => "txt".to_string(),
|
||||||
}
|
}
|
||||||
} else if let Some(filename) = path.file_name().and_then(|name| name.to_str()) {
|
} else {
|
||||||
|
// Check filename for special cases
|
||||||
|
if let Some(filename) = path.file_name().and_then(|name| name.to_str()) {
|
||||||
match filename.to_lowercase().as_str() {
|
match filename.to_lowercase().as_str() {
|
||||||
"dockerfile" => "dockerfile".to_string(),
|
"dockerfile" => "dockerfile".to_string(),
|
||||||
"makefile" => "makefile".to_string(),
|
"makefile" => "makefile".to_string(),
|
||||||
"cargo.toml" | "pyproject.toml" => "toml".to_string(),
|
"cargo.toml" | "pyproject.toml" => "toml".to_string(),
|
||||||
"package.json" => "json".to_string(),
|
"package.json" => "json".to_string(),
|
||||||
_ => default_lang,
|
_ => "txt".to_string(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
default_lang
|
"txt".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"txt".to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,77 +26,6 @@ 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, "Cache State")
|
|
||||||
.on_hover_text(
|
|
||||||
"Unsaved changes will be cached 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();
|
|
||||||
}
|
|
||||||
if ui
|
|
||||||
.checkbox(&mut app.syntax_highlighting, "Syntax Highlighting")
|
|
||||||
.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();
|
|
||||||
}
|
|
||||||
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);
|
||||||
|
|
||||||
@ -131,7 +60,7 @@ pub(crate) fn preferences_window(app: &mut TextEditor, ctx: &egui::Context) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
app.apply_font_settings(ctx);
|
app.apply_font_settings_with_ui(ctx, ui);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -170,15 +99,17 @@ 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(ctx);
|
app.apply_font_settings_with_ui(ctx, ui);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.font_size_input = None;
|
app.font_size_input = None;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(12.0);
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
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