diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bfa7b2b..f7063bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - components: clippy + components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 with: workspaces: src-tauri @@ -46,5 +46,6 @@ jobs: sudo apt-get install -y \ libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev \ librsvg2-dev libsoup-3.0-dev patchelf + - run: cargo fmt --manifest-path src-tauri/Cargo.toml --check - run: cargo test --manifest-path src-tauri/Cargo.toml - run: cargo clippy --manifest-path src-tauri/Cargo.toml --all-targets -- -D warnings diff --git a/README.md b/README.md index 3a586a5..be8f228 100644 --- a/README.md +++ b/README.md @@ -211,4 +211,3 @@ Key modules: - Metadata storage approach review - Mobile view - Mobile app - diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 795b9b7..d860e1e 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 81d67f5..bc15b7e 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -242,7 +242,12 @@ struct Found { /// separators. Skips dot-directories (`.git`, `.obsidian`, …) and never follows symlinks (so the /// walk can't escape the folder or loop). Non-`.md` entries — the sidecar, `.gnkeep`, `*.gn-tmp`, /// `*.rename-tmp` — are filtered by `is_md`. `full` reads the whole body; otherwise just the head. -fn collect_md(root: &Path, current: &Path, full: bool, out: &mut Vec) -> std::io::Result<()> { +fn collect_md( + root: &Path, + current: &Path, + full: bool, + out: &mut Vec, +) -> std::io::Result<()> { for entry in fs::read_dir(current)? { let entry = entry?; let file_type = entry.file_type()?; @@ -896,8 +901,8 @@ async fn open_workspace_window( let mut config = app.config().app.windows[0].clone(); config.label = label.clone(); config.title = title; - let built = tauri::WebviewWindowBuilder::from_config(&app, &config) - .and_then(|builder| builder.build()); + let built = + tauri::WebviewWindowBuilder::from_config(&app, &config).and_then(|builder| builder.build()); // Clear the pending marker regardless of outcome; on a build failure the page never loaded, // so drop the pre-assigned label too (nothing else will ever release it). { @@ -1022,8 +1027,8 @@ async fn open_note_window( config.x = Some(mpos.x + ((msize.width - NOTE_WINDOW_WIDTH).max(0.0) / 2.0) + step); config.y = Some(mpos.y + ((msize.height - NOTE_WINDOW_HEIGHT).max(0.0) / 3.0) + step); } - let built = tauri::WebviewWindowBuilder::from_config(&app, &config) - .and_then(|builder| builder.build()); + let built = + tauri::WebviewWindowBuilder::from_config(&app, &config).and_then(|builder| builder.build()); { let mut st = state.0.lock().unwrap(); st.pending.remove(&pending_key); @@ -1164,7 +1169,10 @@ fn apply_macos_chrome(window: &tauri::WebviewWindow) { }); // Colors mirror Gravity's base background (dark tuned in index.css). Default to dark if // the theme can't be read — "better dark than white" (the requested fallback). - let dark = window.theme().map(|t| t == tauri::Theme::Dark).unwrap_or(true); + let dark = window + .theme() + .map(|t| t == tauri::Theme::Dark) + .unwrap_or(true); let bg = if dark { tauri::window::Color(33, 30, 26, 255) } else { @@ -1185,13 +1193,7 @@ fn build_menu(app: &tauri::AppHandle) -> tauri::Result, - )?; + let about = MenuItem::with_id(app, "about", format!("About {name}"), true, None::<&str>)?; let app_menu = Submenu::with_items( app, &name, @@ -1233,13 +1235,8 @@ fn build_menu(app: &tauri::AppHandle) -> tauri::Result { + tauri::WindowEvent::CloseRequested { api, .. } if window.label() == "main" => { api.prevent_close(); let _ = window.hide(); } @@ -1389,7 +1386,8 @@ pub fn run() { // when nothing is visible, so it doesn't yank main above an open workspace window. #[cfg(target_os = "macos")] if let tauri::RunEvent::Reopen { - has_visible_windows, .. + has_visible_windows, + .. } = event { if !has_visible_windows { @@ -1412,7 +1410,8 @@ mod tests { /// A fresh, unique temp directory for one test (removed at the end). fn temp_dir() -> PathBuf { let n = COUNTER.fetch_add(1, Ordering::SeqCst); - let dir = std::env::temp_dir().join(format!("gravity-notes-test-{}-{n}", std::process::id())); + let dir = + std::env::temp_dir().join(format!("gravity-notes-test-{}-{n}", std::process::id())); let _ = fs::remove_dir_all(&dir); fs::create_dir_all(&dir).unwrap(); dir @@ -1428,7 +1427,9 @@ mod tests { notes_write(s(&dir), "Work/Sub/Note.md".into(), "hello".into()).unwrap(); // The intermediate folders were created and the file is readable by its path-id. - let read = notes_read_opt(s(&dir), "Work/Sub/Note.md".into()).unwrap().unwrap(); + let read = notes_read_opt(s(&dir), "Work/Sub/Note.md".into()) + .unwrap() + .unwrap(); assert_eq!(read.content, "hello"); assert!(dir.join("Work").join("Sub").join("Note.md").is_file()); @@ -1450,13 +1451,25 @@ mod tests { fs::write(dir.join("node_modules").join("README.md"), "dep").unwrap(); fs::write(dir.join("node_modules").join("pkg").join("Index.md"), "dep").unwrap(); fs::create_dir_all(dir.join("Work").join("node_modules")).unwrap(); - fs::write(dir.join("Work").join("node_modules").join("Nested.md"), "dep").unwrap(); + fs::write( + dir.join("Work").join("node_modules").join("Nested.md"), + "dep", + ) + .unwrap(); - let mut ids: Vec = notes_list(s(&dir)).unwrap().into_iter().map(|n| n.name).collect(); + let mut ids: Vec = notes_list(s(&dir)) + .unwrap() + .into_iter() + .map(|n| n.name) + .collect(); ids.sort(); assert_eq!(ids, vec!["Inbox.md", "Work/Roadmap.md", "Work/Sub/Deep.md"]); - let mut all: Vec = notes_read_all(s(&dir)).unwrap().into_iter().map(|n| n.name).collect(); + let mut all: Vec = notes_read_all(s(&dir)) + .unwrap() + .into_iter() + .map(|n| n.name) + .collect(); all.sort(); assert_eq!(all, vec!["Inbox.md", "Work/Roadmap.md", "Work/Sub/Deep.md"]); @@ -1501,7 +1514,11 @@ mod tests { // The dot-directory is invisible to the recursive note + folder walks (Root.md aside). assert_eq!( - notes_list(s(&dir)).unwrap().into_iter().map(|n| n.name).collect::>(), + notes_list(s(&dir)) + .unwrap() + .into_iter() + .map(|n| n.name) + .collect::>(), vec!["Root.md"] ); assert!(notes_list_folders(s(&dir)).unwrap().is_empty()); @@ -1537,8 +1554,12 @@ mod tests { notes_rename(s(&dir), "Inbox/Note.md".into(), "Archive/Note.md".into()).unwrap(); - assert!(notes_stat(s(&dir), "Inbox/Note.md".into()).unwrap().is_none()); - let moved = notes_read_opt(s(&dir), "Archive/Note.md".into()).unwrap().unwrap(); + assert!(notes_stat(s(&dir), "Inbox/Note.md".into()) + .unwrap() + .is_none()); + let moved = notes_read_opt(s(&dir), "Archive/Note.md".into()) + .unwrap() + .unwrap(); assert_eq!(moved.content, "keep"); let _ = fs::remove_dir_all(&dir); @@ -1628,7 +1649,12 @@ mod tests { // The whole subtree moved under Archive/, and the now-empty Work/ was pruned. assert!(!dir.join("Work").exists()); assert!(dir.join("Archive").join("Work").join("A.md").is_file()); - assert!(dir.join("Archive").join("Work").join("Sub").join("B.md").is_file()); + assert!(dir + .join("Archive") + .join("Work") + .join("Sub") + .join("B.md") + .is_file()); let _ = fs::remove_dir_all(&dir); } @@ -1657,7 +1683,10 @@ mod tests { assert_eq!(read, Some(bytes)); // A missing attachment reads as None (mapped to not-found TS-side), not an error. - assert_eq!(attachment_read(s(&dir), "Attachments/missing.png".into()).unwrap(), None); + assert_eq!( + attachment_read(s(&dir), "Attachments/missing.png".into()).unwrap(), + None + ); // Both arguments are containment-guarded. assert!(attachment_write(s(&dir), "../evil.png".into(), vec![1]).is_err()); @@ -1677,16 +1706,26 @@ mod tests { // A dotfile in the folder must be ignored by the listing. fs::write(dir.join("Attachments").join(".keep"), b"").unwrap(); - let mut names: Vec = - attachment_list(s(&dir)).unwrap().into_iter().map(|a| a.name).collect(); + let mut names: Vec = attachment_list(s(&dir)) + .unwrap() + .into_iter() + .map(|a| a.name) + .collect(); names.sort(); assert_eq!(names, vec!["cat.png", "dog.gif"]); - let cat = attachment_list(s(&dir)).unwrap().into_iter().find(|a| a.name == "cat.png").unwrap(); + let cat = attachment_list(s(&dir)) + .unwrap() + .into_iter() + .find(|a| a.name == "cat.png") + .unwrap(); assert_eq!(cat.size, 3.0); attachment_remove(s(&dir), "Attachments/cat.png".into()).unwrap(); - let names: Vec = - attachment_list(s(&dir)).unwrap().into_iter().map(|a| a.name).collect(); + let names: Vec = attachment_list(s(&dir)) + .unwrap() + .into_iter() + .map(|a| a.name) + .collect(); assert_eq!(names, vec!["dog.gif"]); // Removing a missing file is a no-op; traversal is rejected. assert!(attachment_remove(s(&dir), "Attachments/gone.png".into()).is_ok()); @@ -1703,7 +1742,11 @@ mod tests { // A stray .md inside Attachments/ must not be picked up as a note. fs::write(dir.join("Attachments").join("Stray.md"), "nope").unwrap(); - let notes: Vec = notes_list(s(&dir)).unwrap().into_iter().map(|n| n.name).collect(); + let notes: Vec = notes_list(s(&dir)) + .unwrap() + .into_iter() + .map(|n| n.name) + .collect(); assert_eq!(notes, vec!["Note.md"]); assert!(notes_list_folders(s(&dir)).unwrap().is_empty()); @@ -1773,8 +1816,20 @@ mod tests { // Renaming A onto the existing, distinct B must fail and leave both intact. assert!(notes_rename(s(&dir), "A.md".into(), "B.md".into()).is_err()); - assert_eq!(notes_read_opt(s(&dir), "A.md".into()).unwrap().unwrap().content, "aaa"); - assert_eq!(notes_read_opt(s(&dir), "B.md".into()).unwrap().unwrap().content, "bbb"); + assert_eq!( + notes_read_opt(s(&dir), "A.md".into()) + .unwrap() + .unwrap() + .content, + "aaa" + ); + assert_eq!( + notes_read_opt(s(&dir), "B.md".into()) + .unwrap() + .unwrap() + .content, + "bbb" + ); let _ = fs::remove_dir_all(&dir); } @@ -1818,7 +1873,10 @@ mod tests { Some("ws-1".to_string()) ); // …but never the asking window itself (a switch must not "focus" its own window). - assert_eq!(window_label_for_workspace(&map, "tauri:/b", Some("ws-1")), None); + assert_eq!( + window_label_for_workspace(&map, "tauri:/b", Some("ws-1")), + None + ); assert_eq!( window_label_for_workspace(&map, "tauri:/a", Some("ws-1")), Some("main".to_string()) @@ -1857,7 +1915,13 @@ mod tests { assert_eq!(notes.get("note-2").map(String::as_str), Some("Old.md")); // A rename of a note no window shows is a no-op. - remap_note_windows(&labels, &mut notes, "tauri:/a", "Missing.md", "Elsewhere.md"); + remap_note_windows( + &labels, + &mut notes, + "tauri:/a", + "Missing.md", + "Elsewhere.md", + ); assert_eq!(notes.get("note-1").map(String::as_str), Some("New.md")); } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index ad5fe83..69c3a72 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - app_lib::run(); + app_lib::run(); }