2025-07-15 00:42:01 -04:00
|
|
|
use crate::app::tab::Tab;
|
2025-07-15 11:07:41 -04:00
|
|
|
use crate::app::TextEditor;
|
2025-07-05 14:42:45 -04:00
|
|
|
use std::fs;
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
pub(crate) fn new_file(app: &mut TextEditor) {
|
|
|
|
|
app.add_new_tab();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-26 11:50:48 -04:00
|
|
|
fn is_text_file(path: &PathBuf) -> bool {
|
|
|
|
|
if let Some(extension) = path.extension().and_then(|s| s.to_str()) {
|
|
|
|
|
matches!(
|
|
|
|
|
extension.to_lowercase().as_str(),
|
|
|
|
|
"txt"
|
|
|
|
|
| "md"
|
|
|
|
|
| "markdown"
|
|
|
|
|
| "rs"
|
|
|
|
|
| "py"
|
|
|
|
|
| "js"
|
|
|
|
|
| "ts"
|
|
|
|
|
| "tsx"
|
|
|
|
|
| "jsx"
|
|
|
|
|
| "c"
|
|
|
|
|
| "cpp"
|
|
|
|
|
| "cc"
|
|
|
|
|
| "cxx"
|
|
|
|
|
| "h"
|
|
|
|
|
| "hpp"
|
|
|
|
|
| "java"
|
|
|
|
|
| "go"
|
|
|
|
|
| "php"
|
|
|
|
|
| "rb"
|
|
|
|
|
| "cs"
|
|
|
|
|
| "swift"
|
|
|
|
|
| "kt"
|
|
|
|
|
| "scala"
|
|
|
|
|
| "sh"
|
|
|
|
|
| "bash"
|
|
|
|
|
| "zsh"
|
|
|
|
|
| "fish"
|
|
|
|
|
| "html"
|
|
|
|
|
| "htm"
|
|
|
|
|
| "xml"
|
|
|
|
|
| "css"
|
|
|
|
|
| "scss"
|
|
|
|
|
| "sass"
|
|
|
|
|
| "json"
|
|
|
|
|
| "yaml"
|
|
|
|
|
| "yml"
|
|
|
|
|
| "toml"
|
|
|
|
|
| "sql"
|
|
|
|
|
| "lua"
|
|
|
|
|
| "vim"
|
|
|
|
|
| "dockerfile"
|
|
|
|
|
| "makefile"
|
|
|
|
|
| "gitignore"
|
|
|
|
|
| "conf"
|
|
|
|
|
| "cfg"
|
|
|
|
|
| "ini"
|
|
|
|
|
| "log"
|
|
|
|
|
| "csv"
|
|
|
|
|
| "tsv"
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
// Files without extensions might be text files, but let's be conservative
|
|
|
|
|
// and only include them if they're small and readable
|
|
|
|
|
if let Ok(metadata) = fs::metadata(path) {
|
|
|
|
|
metadata.len() < 1024 * 1024 // Only consider files smaller than 1MB
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn open_files_from_directory(
|
|
|
|
|
app: &mut TextEditor,
|
|
|
|
|
dir_path: PathBuf,
|
|
|
|
|
) -> Result<usize, String> {
|
|
|
|
|
if !dir_path.is_dir() {
|
|
|
|
|
return Err(format!("{} is not a directory", dir_path.display()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let entries = fs::read_dir(&dir_path)
|
|
|
|
|
.map_err(|e| format!("Failed to read directory {}: {}", dir_path.display(), e))?;
|
|
|
|
|
|
|
|
|
|
let mut opened_count = 0;
|
|
|
|
|
let mut text_files: Vec<PathBuf> = Vec::new();
|
|
|
|
|
|
|
|
|
|
// Collect all text files in the directory
|
|
|
|
|
for entry in entries {
|
|
|
|
|
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
|
|
|
|
|
let path = entry.path();
|
|
|
|
|
|
|
|
|
|
if path.is_file() && is_text_file(&path) {
|
|
|
|
|
text_files.push(path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort files by name for consistent ordering
|
|
|
|
|
text_files.sort();
|
|
|
|
|
|
|
|
|
|
// Open each text file
|
|
|
|
|
for file_path in text_files {
|
|
|
|
|
match open_file_from_path(app, file_path.clone()) {
|
|
|
|
|
Ok(()) => opened_count += 1,
|
|
|
|
|
Err(e) => eprintln!("Warning: {}", e),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if opened_count == 0 {
|
|
|
|
|
Err(format!(
|
|
|
|
|
"No text files found in directory {}",
|
|
|
|
|
dir_path.display()
|
|
|
|
|
))
|
|
|
|
|
} else {
|
|
|
|
|
Ok(opened_count)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-05 14:42:45 -04:00
|
|
|
pub(crate) fn open_file(app: &mut TextEditor) {
|
|
|
|
|
if let Some(path) = rfd::FileDialog::new()
|
|
|
|
|
.add_filter("Text files", &["*"])
|
|
|
|
|
.pick_file()
|
|
|
|
|
{
|
|
|
|
|
match fs::read_to_string(&path) {
|
|
|
|
|
Ok(content) => {
|
|
|
|
|
let should_replace_current_tab = if let Some(active_tab) = app.get_active_tab() {
|
|
|
|
|
active_tab.file_path.is_none()
|
|
|
|
|
&& active_tab.content.is_empty()
|
|
|
|
|
&& !active_tab.is_modified
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if should_replace_current_tab {
|
|
|
|
|
if let Some(active_tab) = app.get_active_tab_mut() {
|
2025-07-16 17:34:11 -04:00
|
|
|
let title = path
|
|
|
|
|
.file_name()
|
|
|
|
|
.and_then(|n| n.to_str())
|
|
|
|
|
.unwrap_or("Untitled");
|
2025-07-05 14:42:45 -04:00
|
|
|
active_tab.content = content;
|
2025-07-16 17:20:09 -04:00
|
|
|
active_tab.file_path = Some(path.to_path_buf());
|
|
|
|
|
active_tab.title = title.to_string();
|
2025-07-16 13:27:31 -04:00
|
|
|
active_tab.mark_as_saved();
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
2025-07-16 13:27:31 -04:00
|
|
|
app.text_needs_processing = true;
|
2025-07-05 14:42:45 -04:00
|
|
|
} else {
|
|
|
|
|
let new_tab = Tab::new_with_file(content, path);
|
|
|
|
|
app.tabs.push(new_tab);
|
|
|
|
|
app.active_tab_index = app.tabs.len() - 1;
|
2025-07-16 13:27:31 -04:00
|
|
|
app.text_needs_processing = true;
|
|
|
|
|
}
|
|
|
|
|
if app.show_find && !app.find_query.is_empty() {
|
|
|
|
|
app.update_find_matches();
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
2025-07-23 13:17:10 -04:00
|
|
|
|
2025-07-23 12:47:26 -04:00
|
|
|
if let Err(e) = app.save_state_cache() {
|
|
|
|
|
eprintln!("Failed to save state cache: {e}");
|
|
|
|
|
}
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
|
|
|
|
Err(err) => {
|
2025-07-15 00:42:01 -04:00
|
|
|
eprintln!("Failed to open file: {err}");
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-26 11:50:48 -04:00
|
|
|
pub(crate) fn open_file_from_path(app: &mut TextEditor, path: PathBuf) -> Result<(), String> {
|
|
|
|
|
match fs::read_to_string(&path) {
|
|
|
|
|
Ok(content) => {
|
|
|
|
|
let should_replace_current_tab = if let Some(active_tab) = app.get_active_tab() {
|
|
|
|
|
active_tab.file_path.is_none()
|
|
|
|
|
&& active_tab.content.is_empty()
|
|
|
|
|
&& !active_tab.is_modified
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if should_replace_current_tab {
|
|
|
|
|
if let Some(active_tab) = app.get_active_tab_mut() {
|
|
|
|
|
let title = path
|
|
|
|
|
.file_name()
|
|
|
|
|
.and_then(|n| n.to_str())
|
|
|
|
|
.unwrap_or("Untitled");
|
|
|
|
|
active_tab.content = content;
|
|
|
|
|
active_tab.file_path = Some(path.to_path_buf());
|
|
|
|
|
active_tab.title = title.to_string();
|
|
|
|
|
active_tab.mark_as_saved();
|
|
|
|
|
}
|
|
|
|
|
app.text_needs_processing = true;
|
|
|
|
|
} else {
|
|
|
|
|
let new_tab = Tab::new_with_file(content, path);
|
|
|
|
|
app.tabs.push(new_tab);
|
|
|
|
|
app.active_tab_index = app.tabs.len() - 1;
|
|
|
|
|
app.text_needs_processing = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if app.show_find && !app.find_query.is_empty() {
|
|
|
|
|
app.update_find_matches();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Err(e) = app.save_state_cache() {
|
|
|
|
|
eprintln!("Failed to save state cache: {e}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
Err(err) => Err(format!("Failed to open file {}: {}", path.display(), err)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-05 14:42:45 -04:00
|
|
|
pub(crate) fn save_file(app: &mut TextEditor) {
|
|
|
|
|
if let Some(active_tab) = app.get_active_tab() {
|
|
|
|
|
if let Some(path) = &active_tab.file_path {
|
2025-07-16 17:20:09 -04:00
|
|
|
save_to_path(app, path.to_path_buf());
|
2025-07-05 14:42:45 -04:00
|
|
|
} else {
|
|
|
|
|
save_as_file(app);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn save_as_file(app: &mut TextEditor) {
|
|
|
|
|
if let Some(path) = rfd::FileDialog::new()
|
|
|
|
|
.add_filter("Text files", &["*"])
|
|
|
|
|
.save_file()
|
|
|
|
|
{
|
|
|
|
|
save_to_path(app, path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn save_to_path(app: &mut TextEditor, path: PathBuf) {
|
|
|
|
|
if let Some(active_tab) = app.get_active_tab_mut() {
|
|
|
|
|
match fs::write(&path, &active_tab.content) {
|
|
|
|
|
Ok(()) => {
|
2025-07-16 17:34:11 -04:00
|
|
|
let title = path
|
|
|
|
|
.file_name()
|
|
|
|
|
.and_then(|n| n.to_str())
|
|
|
|
|
.unwrap_or("Untitled");
|
2025-07-16 17:20:09 -04:00
|
|
|
active_tab.file_path = Some(path.to_path_buf());
|
|
|
|
|
active_tab.title = title.to_string();
|
2025-07-05 14:42:45 -04:00
|
|
|
active_tab.mark_as_saved();
|
2025-07-23 13:17:10 -04:00
|
|
|
|
2025-07-23 12:47:26 -04:00
|
|
|
if let Err(e) = app.save_state_cache() {
|
|
|
|
|
eprintln!("Failed to save state cache: {e}");
|
|
|
|
|
}
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
|
|
|
|
Err(err) => {
|
2025-07-15 00:42:01 -04:00
|
|
|
eprintln!("Failed to save file: {err}");
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|