add all project files
7
src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
||||
5159
src-tauri/Cargo.lock
generated
Normal file
21
src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "lunar-code"
|
||||
version = "0.1.0"
|
||||
description = "Lunar Code - A lightweight code editor"
|
||||
authors = ["luna"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "lunar_code_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
3
src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
43
src-tauri/capabilities/default.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"dialog:default",
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-save",
|
||||
"dialog:allow-ask",
|
||||
"dialog:allow-confirm",
|
||||
"dialog:allow-message",
|
||||
"fs:default",
|
||||
"fs:read-all",
|
||||
"fs:write-all",
|
||||
"fs:scope-home-recursive",
|
||||
"shell:default",
|
||||
"shell:allow-open",
|
||||
{
|
||||
"identifier": "shell:allow-execute",
|
||||
"allow": [
|
||||
{
|
||||
"name": "exec-sh",
|
||||
"cmd": "sh",
|
||||
"args": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "shell:allow-spawn",
|
||||
"allow": [
|
||||
{
|
||||
"name": "exec-sh",
|
||||
"cmd": "sh",
|
||||
"args": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"shell:allow-stdin-write",
|
||||
"shell:allow-kill"
|
||||
]
|
||||
}
|
||||
BIN
src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 974 B |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 903 B |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
232
src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,232 @@
|
||||
use std::fs;
|
||||
use std::io::BufRead;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct DirEntry {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
pub is_dir: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn read_directory(path: String) -> Result<Vec<DirEntry>, String> {
|
||||
let dir_path = Path::new(&path);
|
||||
if !dir_path.is_dir() {
|
||||
return Err(format!("{} is not a directory", path));
|
||||
}
|
||||
|
||||
let mut entries: Vec<DirEntry> = Vec::new();
|
||||
let read_dir = fs::read_dir(dir_path).map_err(|e| e.to_string())?;
|
||||
|
||||
for entry in read_dir {
|
||||
let entry = entry.map_err(|e| e.to_string())?;
|
||||
let file_name = entry.file_name().to_string_lossy().to_string();
|
||||
|
||||
if file_name.starts_with('.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let file_path = entry.path().to_string_lossy().to_string();
|
||||
let is_dir = entry.path().is_dir();
|
||||
|
||||
entries.push(DirEntry {
|
||||
name: file_name,
|
||||
path: file_path,
|
||||
is_dir,
|
||||
});
|
||||
}
|
||||
|
||||
entries.sort_by(|a, b| {
|
||||
if a.is_dir == b.is_dir {
|
||||
a.name.to_lowercase().cmp(&b.name.to_lowercase())
|
||||
} else if a.is_dir {
|
||||
std::cmp::Ordering::Less
|
||||
} else {
|
||||
std::cmp::Ordering::Greater
|
||||
}
|
||||
});
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn read_file(path: String) -> Result<String, String> {
|
||||
fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {}", path, e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn write_file(path: String, contents: String) -> Result<(), String> {
|
||||
fs::write(&path, contents).map_err(|e| format!("Failed to write {}: {}", path, e))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn create_file(path: String) -> Result<(), String> {
|
||||
if Path::new(&path).exists() {
|
||||
return Err(format!("{} already exists", path));
|
||||
}
|
||||
fs::write(&path, "").map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn create_directory(path: String) -> Result<(), String> {
|
||||
fs::create_dir_all(&path).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn rename_path(old_path: String, new_path: String) -> Result<(), String> {
|
||||
fs::rename(&old_path, &new_path).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn delete_path(path: String) -> Result<(), String> {
|
||||
let p = Path::new(&path);
|
||||
if p.is_dir() {
|
||||
fs::remove_dir_all(p).map_err(|e| e.to_string())
|
||||
} else {
|
||||
fs::remove_file(p).map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct SearchMatch {
|
||||
pub file_path: String,
|
||||
pub file_name: String,
|
||||
pub line_number: usize,
|
||||
pub line_content: String,
|
||||
pub match_start: usize,
|
||||
pub match_end: usize,
|
||||
}
|
||||
|
||||
const SKIP_DIRS: &[&str] = &[
|
||||
"node_modules", "target", ".git", "dist", "build", "__pycache__",
|
||||
".next", ".nuxt", "vendor", ".venv", "venv",
|
||||
];
|
||||
|
||||
fn search_dir(
|
||||
dir: &Path,
|
||||
query: &str,
|
||||
case_sensitive: bool,
|
||||
results: &mut Vec<SearchMatch>,
|
||||
max_results: usize,
|
||||
) {
|
||||
if results.len() >= max_results {
|
||||
return;
|
||||
}
|
||||
|
||||
let read_dir = match fs::read_dir(dir) {
|
||||
Ok(rd) => rd,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let mut entries: Vec<_> = read_dir.filter_map(|e| e.ok()).collect();
|
||||
entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
|
||||
|
||||
for entry in entries {
|
||||
if results.len() >= max_results {
|
||||
return;
|
||||
}
|
||||
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
if name.starts_with('.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
if SKIP_DIRS.contains(&name.as_str()) {
|
||||
continue;
|
||||
}
|
||||
search_dir(&path, query, case_sensitive, results, max_results);
|
||||
} else {
|
||||
search_file(&path, &name, query, case_sensitive, results, max_results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn search_file(
|
||||
path: &Path,
|
||||
name: &str,
|
||||
query: &str,
|
||||
case_sensitive: bool,
|
||||
results: &mut Vec<SearchMatch>,
|
||||
max_results: usize,
|
||||
) {
|
||||
let file = match fs::File::open(path) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
// Skip binary/large files
|
||||
let metadata = match file.metadata() {
|
||||
Ok(m) => m,
|
||||
Err(_) => return,
|
||||
};
|
||||
if metadata.len() > 2_000_000 {
|
||||
return;
|
||||
}
|
||||
|
||||
let reader = std::io::BufReader::new(file);
|
||||
let query_lower = if case_sensitive { query.to_string() } else { query.to_lowercase() };
|
||||
let path_str = path.to_string_lossy().to_string();
|
||||
|
||||
for (i, line) in reader.lines().enumerate() {
|
||||
if results.len() >= max_results {
|
||||
return;
|
||||
}
|
||||
let line = match line {
|
||||
Ok(l) => l,
|
||||
Err(_) => return, // likely binary
|
||||
};
|
||||
let search_line = if case_sensitive { line.clone() } else { line.to_lowercase() };
|
||||
if let Some(pos) = search_line.find(&query_lower) {
|
||||
results.push(SearchMatch {
|
||||
file_path: path_str.clone(),
|
||||
file_name: name.to_string(),
|
||||
line_number: i + 1,
|
||||
line_content: line.chars().take(500).collect(),
|
||||
match_start: pos,
|
||||
match_end: pos + query.len(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn search_in_files(
|
||||
dir: String,
|
||||
query: String,
|
||||
case_sensitive: bool,
|
||||
) -> Result<Vec<SearchMatch>, String> {
|
||||
let dir_path = Path::new(&dir);
|
||||
if !dir_path.is_dir() {
|
||||
return Err(format!("{} is not a directory", dir));
|
||||
}
|
||||
if query.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
search_dir(dir_path, &query, case_sensitive, &mut results, 1000);
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
read_directory,
|
||||
read_file,
|
||||
write_file,
|
||||
create_file,
|
||||
create_directory,
|
||||
rename_path,
|
||||
delete_path,
|
||||
search_in_files,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
6
src-tauri/src/main.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
lunar_code_lib::run()
|
||||
}
|
||||
45
src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Lunar Code",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.luna.lunar-code",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"beforeBuildCommand": "pnpm build",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Lunar Code",
|
||||
"width": 1200,
|
||||
"height": 800,
|
||||
"minWidth": 640,
|
||||
"minHeight": 480
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"fs": {
|
||||
"requireLiteralLeadingDot": false
|
||||
},
|
||||
"shell": {
|
||||
"open": true
|
||||
}
|
||||
}
|
||||
}
|
||||