feat: hide gitignored vault content
This commit is contained in:
@@ -165,6 +165,24 @@ fn ensure_missing_folder(folder_path: &Path, folder_name: &str) -> Result<(), St
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn scan_visible_vault_entries(vault_path: &Path) -> Result<Vec<VaultEntry>, String> {
|
||||
let entries = vault::scan_vault_cached(vault_path)?;
|
||||
Ok(vault::filter_gitignored_entries(
|
||||
vault_path,
|
||||
entries,
|
||||
crate::settings::hide_gitignored_files_enabled(),
|
||||
))
|
||||
}
|
||||
|
||||
fn scan_visible_vault_folders(vault_path: &Path) -> Result<Vec<FolderNode>, String> {
|
||||
let folders = vault::scan_vault_folders(vault_path)?;
|
||||
Ok(vault::filter_gitignored_folders(
|
||||
vault_path,
|
||||
folders,
|
||||
crate::settings::hide_gitignored_files_enabled(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Sync the `title` frontmatter field with the filename on note open.
|
||||
/// Returns `true` if the file was modified (title was absent or desynced).
|
||||
#[tauri::command]
|
||||
@@ -207,12 +225,12 @@ pub fn copy_image_to_vault(
|
||||
|
||||
#[tauri::command]
|
||||
pub fn list_vault(path: PathBuf) -> Result<Vec<VaultEntry>, String> {
|
||||
with_expanded_vault_root(path.as_path(), vault::scan_vault_cached)
|
||||
with_expanded_vault_root(path.as_path(), scan_visible_vault_entries)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn list_vault_folders(path: PathBuf) -> Result<Vec<FolderNode>, String> {
|
||||
with_expanded_vault_root(path.as_path(), vault::scan_vault_folders)
|
||||
with_expanded_vault_root(path.as_path(), scan_visible_vault_folders)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -83,8 +83,14 @@ pub async fn reload_vault(
|
||||
let path = expand_tilde(&path).into_owned();
|
||||
crate::sync_vault_asset_scope(&app_handle, Path::new(&path))?;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
vault::invalidate_cache(Path::new(&path));
|
||||
vault::scan_vault_cached(Path::new(&path))
|
||||
let vault_path = Path::new(&path);
|
||||
vault::invalidate_cache(vault_path);
|
||||
let entries = vault::scan_vault_cached(vault_path)?;
|
||||
Ok(vault::filter_gitignored_entries(
|
||||
vault_path,
|
||||
entries,
|
||||
crate::settings::hide_gitignored_files_enabled(),
|
||||
))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task panicked: {e}"))?
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
@@ -20,6 +20,14 @@ pub struct SearchResponse {
|
||||
pub mode: String,
|
||||
}
|
||||
|
||||
pub struct SearchOptions<'a> {
|
||||
pub vault_path: &'a str,
|
||||
pub query: &'a str,
|
||||
pub mode: &'a str,
|
||||
pub limit: usize,
|
||||
pub hide_gitignored_files: bool,
|
||||
}
|
||||
|
||||
fn extract_snippet(content: &str, query_lower: &str) -> String {
|
||||
let content_lower = content.to_lowercase();
|
||||
let pos = match content_lower.find(query_lower) {
|
||||
@@ -63,26 +71,45 @@ pub fn search_vault(
|
||||
_mode: &str,
|
||||
limit: usize,
|
||||
) -> Result<SearchResponse, String> {
|
||||
search_vault_with_options(SearchOptions {
|
||||
vault_path,
|
||||
query,
|
||||
mode: _mode,
|
||||
limit,
|
||||
hide_gitignored_files: crate::settings::hide_gitignored_files_enabled(),
|
||||
})
|
||||
}
|
||||
|
||||
fn is_markdown_search_candidate(path: &Path) -> bool {
|
||||
if !path.extension().is_some_and(|ext| ext == "md") {
|
||||
return false;
|
||||
}
|
||||
|
||||
!path
|
||||
.components()
|
||||
.any(|component| component.as_os_str().to_string_lossy().starts_with('.'))
|
||||
}
|
||||
|
||||
fn collect_markdown_paths(vault_dir: &Path, hide_gitignored_files: bool) -> Vec<PathBuf> {
|
||||
let paths = WalkDir::new(vault_dir)
|
||||
.into_iter()
|
||||
.filter_map(|entry| entry.ok())
|
||||
.map(|entry| entry.into_path())
|
||||
.filter(|path| is_markdown_search_candidate(path))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
crate::vault::filter_gitignored_paths(vault_dir, paths, hide_gitignored_files)
|
||||
}
|
||||
|
||||
pub fn search_vault_with_options(options: SearchOptions<'_>) -> Result<SearchResponse, String> {
|
||||
let start = Instant::now();
|
||||
let query_lower = query.to_lowercase();
|
||||
let vault_dir = Path::new(vault_path);
|
||||
let query_lower = options.query.to_lowercase();
|
||||
let vault_dir = Path::new(options.vault_path);
|
||||
|
||||
let mut results: Vec<SearchResult> = Vec::new();
|
||||
|
||||
for entry in WalkDir::new(vault_dir).into_iter().filter_map(|e| e.ok()) {
|
||||
let path = entry.path();
|
||||
if !path.extension().is_some_and(|ext| ext == "md") {
|
||||
continue;
|
||||
}
|
||||
// Skip hidden dirs and .laputa config
|
||||
if path
|
||||
.components()
|
||||
.any(|c| c.as_os_str().to_string_lossy().starts_with('.'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let content = match std::fs::read_to_string(path) {
|
||||
for path in collect_markdown_paths(vault_dir, options.hide_gitignored_files) {
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
Err(_) => continue,
|
||||
};
|
||||
@@ -117,15 +144,15 @@ pub fn search_vault(
|
||||
.partial_cmp(&a.score)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
results.truncate(limit);
|
||||
results.truncate(options.limit);
|
||||
|
||||
let elapsed_ms = start.elapsed().as_millis() as u64;
|
||||
|
||||
Ok(SearchResponse {
|
||||
results,
|
||||
elapsed_ms,
|
||||
query: query.to_string(),
|
||||
mode: "keyword".to_string(),
|
||||
query: options.query.to_string(),
|
||||
mode: options.mode.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -135,6 +162,14 @@ mod tests {
|
||||
use std::fs;
|
||||
use tempfile::Builder;
|
||||
|
||||
fn init_git_repo(root: &Path) {
|
||||
crate::hidden_command("git")
|
||||
.args(["init"])
|
||||
.current_dir(root)
|
||||
.output()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_snippet_basic() {
|
||||
let content = "line one\nline with keyword here\nline three";
|
||||
@@ -188,4 +223,38 @@ mod tests {
|
||||
assert_eq!(response.results.len(), 1);
|
||||
assert_eq!(response.results[0].title, "Updated Display Title");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_search_vault_hides_gitignored_notes_when_enabled() {
|
||||
let dir = Builder::new()
|
||||
.prefix("search-gitignored-")
|
||||
.tempdir_in(std::env::current_dir().unwrap())
|
||||
.unwrap();
|
||||
init_git_repo(dir.path());
|
||||
fs::create_dir_all(dir.path().join("ignored")).unwrap();
|
||||
fs::write(dir.path().join(".gitignore"), "ignored/\n").unwrap();
|
||||
fs::write(dir.path().join("visible.md"), "# Visible\n\nneedle").unwrap();
|
||||
fs::write(dir.path().join("ignored/hidden.md"), "# Hidden\n\nneedle").unwrap();
|
||||
|
||||
let hidden = search_vault_with_options(SearchOptions {
|
||||
vault_path: dir.path().to_str().unwrap(),
|
||||
query: "needle",
|
||||
mode: "keyword",
|
||||
limit: 10,
|
||||
hide_gitignored_files: true,
|
||||
})
|
||||
.unwrap();
|
||||
let shown = search_vault_with_options(SearchOptions {
|
||||
vault_path: dir.path().to_str().unwrap(),
|
||||
query: "needle",
|
||||
mode: "keyword",
|
||||
limit: 10,
|
||||
hide_gitignored_files: false,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hidden.results.len(), 1);
|
||||
assert_eq!(hidden.results[0].title, "Visible");
|
||||
assert_eq!(shown.results.len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::path::PathBuf;
|
||||
const APP_CONFIG_DIR: &str = "com.tolaria.app";
|
||||
const LEGACY_APP_CONFIG_DIR: &str = "com.laputa.app";
|
||||
const SUPPORTED_DEFAULT_AI_AGENTS: &[&str] = &["claude_code", "codex", "opencode", "pi"];
|
||||
pub const DEFAULT_HIDE_GITIGNORED_FILES: bool = true;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||
pub struct Settings {
|
||||
@@ -22,6 +23,7 @@ pub struct Settings {
|
||||
pub ui_language: Option<String>,
|
||||
pub initial_h1_auto_rename_enabled: Option<bool>,
|
||||
pub default_ai_agent: Option<String>,
|
||||
pub hide_gitignored_files: Option<bool>,
|
||||
}
|
||||
|
||||
fn normalize_optional_string(value: Option<String>) -> Option<String> {
|
||||
@@ -63,6 +65,18 @@ pub fn normalize_theme_mode(value: Option<&str>) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_hide_gitignored_files(settings: &Settings) -> bool {
|
||||
settings
|
||||
.hide_gitignored_files
|
||||
.unwrap_or(DEFAULT_HIDE_GITIGNORED_FILES)
|
||||
}
|
||||
|
||||
pub fn hide_gitignored_files_enabled() -> bool {
|
||||
get_settings()
|
||||
.map(|settings| should_hide_gitignored_files(&settings))
|
||||
.unwrap_or(DEFAULT_HIDE_GITIGNORED_FILES)
|
||||
}
|
||||
|
||||
fn canonical_language_code(value: &str) -> Option<String> {
|
||||
let code = value.trim().replace('_', "-").to_ascii_lowercase();
|
||||
if code.is_empty() {
|
||||
@@ -111,6 +125,7 @@ fn normalize_settings(settings: Settings) -> Settings {
|
||||
ui_language: normalize_ui_language(settings.ui_language.as_deref()),
|
||||
initial_h1_auto_rename_enabled: settings.initial_h1_auto_rename_enabled,
|
||||
default_ai_agent: normalize_default_ai_agent(settings.default_ai_agent.as_deref()),
|
||||
hide_gitignored_files: settings.hide_gitignored_files,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +268,7 @@ mod tests {
|
||||
ui_language: Some("zh-Hans".to_string()),
|
||||
initial_h1_auto_rename_enabled: Some(false),
|
||||
default_ai_agent: Some("codex".to_string()),
|
||||
hide_gitignored_files: Some(false),
|
||||
};
|
||||
let json = serde_json::to_string(&settings).unwrap();
|
||||
let parsed: Settings = serde_json::from_str(&json).unwrap();
|
||||
@@ -280,6 +296,7 @@ mod tests {
|
||||
ui_language: Some("zh-Hans".to_string()),
|
||||
initial_h1_auto_rename_enabled: Some(false),
|
||||
default_ai_agent: Some("codex".to_string()),
|
||||
hide_gitignored_files: Some(false),
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(loaded.auto_pull_interval_minutes, Some(10));
|
||||
@@ -292,6 +309,20 @@ mod tests {
|
||||
assert_eq!(loaded.ui_language.as_deref(), Some("zh-Hans"));
|
||||
assert_eq!(loaded.initial_h1_auto_rename_enabled, Some(false));
|
||||
assert_eq!(loaded.default_ai_agent.as_deref(), Some("codex"));
|
||||
assert_eq!(loaded.hide_gitignored_files, Some(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gitignored_files_are_hidden_by_default() {
|
||||
assert!(should_hide_gitignored_files(&Settings::default()));
|
||||
assert!(should_hide_gitignored_files(&Settings {
|
||||
hide_gitignored_files: Some(true),
|
||||
..Default::default()
|
||||
}));
|
||||
assert!(!should_hide_gitignored_files(&Settings {
|
||||
hide_gitignored_files: Some(false),
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
300
src-tauri/src/vault/ignored.rs
Normal file
300
src-tauri/src/vault/ignored.rs
Normal file
@@ -0,0 +1,300 @@
|
||||
use super::{FolderNode, VaultEntry};
|
||||
use std::collections::HashSet;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Stdio;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
fn normalize_relative_path(path: &str) -> String {
|
||||
path.replace('\\', "/")
|
||||
.trim_start_matches("./")
|
||||
.trim_matches('/')
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn relative_path(vault_path: &Path, path: &Path) -> Option<String> {
|
||||
let relative = path.strip_prefix(vault_path).ok()?;
|
||||
let normalized = normalize_relative_path(relative.to_string_lossy().as_ref());
|
||||
(!normalized.is_empty()).then_some(normalized)
|
||||
}
|
||||
|
||||
fn should_descend_for_gitignore(entry: &DirEntry) -> bool {
|
||||
entry.depth() == 0 || entry.file_name().to_string_lossy() != ".git"
|
||||
}
|
||||
|
||||
fn has_gitignore_file(vault_path: &Path) -> bool {
|
||||
WalkDir::new(vault_path)
|
||||
.follow_links(false)
|
||||
.into_iter()
|
||||
.filter_entry(should_descend_for_gitignore)
|
||||
.filter_map(Result::ok)
|
||||
.any(|entry| {
|
||||
entry.file_type().is_file() && entry.file_name().to_string_lossy() == ".gitignore"
|
||||
})
|
||||
}
|
||||
|
||||
fn run_git_check_ignore(vault_path: &Path, relative_paths: &[String]) -> Option<String> {
|
||||
let mut child = crate::hidden_command("git")
|
||||
.args(["check-ignore", "--no-index", "--stdin"])
|
||||
.current_dir(vault_path)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.ok()?;
|
||||
|
||||
{
|
||||
let stdin = child.stdin.as_mut()?;
|
||||
for path in relative_paths {
|
||||
writeln!(stdin, "{path}").ok()?;
|
||||
}
|
||||
}
|
||||
|
||||
let output = child.wait_with_output().ok()?;
|
||||
if output.status.success() || output.status.code() == Some(1) {
|
||||
return Some(String::from_utf8_lossy(&output.stdout).to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn ignored_relative_paths(vault_path: &Path, relative_paths: &[String]) -> HashSet<String> {
|
||||
if relative_paths.is_empty() || !has_gitignore_file(vault_path) {
|
||||
return HashSet::new();
|
||||
}
|
||||
|
||||
let mut candidates = relative_paths
|
||||
.iter()
|
||||
.map(|path| normalize_relative_path(path))
|
||||
.filter(|path| !path.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
candidates.sort();
|
||||
candidates.dedup();
|
||||
|
||||
run_git_check_ignore(vault_path, &candidates)
|
||||
.unwrap_or_default()
|
||||
.lines()
|
||||
.map(normalize_relative_path)
|
||||
.filter(|path| !path.is_empty())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn filter_gitignored_items<T>(
|
||||
vault_path: &Path,
|
||||
items: Vec<T>,
|
||||
hide_enabled: bool,
|
||||
relative_for: impl Fn(&T) -> Option<String>,
|
||||
) -> Vec<T> {
|
||||
if !hide_enabled || items.is_empty() {
|
||||
return items;
|
||||
}
|
||||
|
||||
let relative_paths = items.iter().filter_map(&relative_for).collect::<Vec<_>>();
|
||||
let ignored = ignored_relative_paths(vault_path, &relative_paths);
|
||||
if ignored.is_empty() {
|
||||
return items;
|
||||
}
|
||||
|
||||
items
|
||||
.into_iter()
|
||||
.filter(|item| {
|
||||
relative_for(item)
|
||||
.map(|relative| !ignored.contains(&relative))
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn filter_gitignored_paths(
|
||||
vault_path: &Path,
|
||||
paths: Vec<PathBuf>,
|
||||
hide_enabled: bool,
|
||||
) -> Vec<PathBuf> {
|
||||
filter_gitignored_items(vault_path, paths, hide_enabled, |path| {
|
||||
relative_path(vault_path, path)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn filter_gitignored_entries(
|
||||
vault_path: &Path,
|
||||
entries: Vec<VaultEntry>,
|
||||
hide_enabled: bool,
|
||||
) -> Vec<VaultEntry> {
|
||||
filter_gitignored_items(vault_path, entries, hide_enabled, |entry| {
|
||||
relative_path(vault_path, Path::new(&entry.path))
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_folder_queries(nodes: &[FolderNode], queries: &mut Vec<String>) {
|
||||
for node in nodes {
|
||||
let relative = normalize_relative_path(&node.path);
|
||||
if !relative.is_empty() {
|
||||
queries.push(relative.clone());
|
||||
queries.push(format!("{relative}/"));
|
||||
}
|
||||
collect_folder_queries(&node.children, queries);
|
||||
}
|
||||
}
|
||||
|
||||
fn path_or_parent_is_ignored(relative_path: &str, ignored: &HashSet<String>) -> bool {
|
||||
if ignored.contains(relative_path) {
|
||||
return true;
|
||||
}
|
||||
let mut current = relative_path;
|
||||
while let Some((parent, _)) = current.rsplit_once('/') {
|
||||
if ignored.contains(parent) {
|
||||
return true;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn filter_folder_nodes(nodes: Vec<FolderNode>, ignored: &HashSet<String>) -> Vec<FolderNode> {
|
||||
nodes
|
||||
.into_iter()
|
||||
.filter_map(|mut node| {
|
||||
let relative = normalize_relative_path(&node.path);
|
||||
if path_or_parent_is_ignored(&relative, ignored) {
|
||||
return None;
|
||||
}
|
||||
node.children = filter_folder_nodes(node.children, ignored);
|
||||
Some(node)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn filter_gitignored_folders(
|
||||
vault_path: &Path,
|
||||
folders: Vec<FolderNode>,
|
||||
hide_enabled: bool,
|
||||
) -> Vec<FolderNode> {
|
||||
if !hide_enabled || folders.is_empty() {
|
||||
return folders;
|
||||
}
|
||||
|
||||
let mut queries = Vec::new();
|
||||
collect_folder_queries(&folders, &mut queries);
|
||||
let ignored = ignored_relative_paths(vault_path, &queries);
|
||||
if ignored.is_empty() {
|
||||
return folders;
|
||||
}
|
||||
|
||||
filter_folder_nodes(folders, &ignored)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn write_file(root: &Path, relative: &str, content: &str) {
|
||||
let path = root.join(relative);
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).unwrap();
|
||||
}
|
||||
fs::write(path, content).unwrap();
|
||||
}
|
||||
|
||||
fn init_git_repo(root: &Path) {
|
||||
crate::hidden_command("git")
|
||||
.args(["init"])
|
||||
.current_dir(root)
|
||||
.output()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn entry(root: &Path, relative: &str) -> VaultEntry {
|
||||
VaultEntry {
|
||||
path: root.join(relative).to_string_lossy().to_string(),
|
||||
filename: relative.rsplit('/').next().unwrap().to_string(),
|
||||
title: relative.to_string(),
|
||||
..VaultEntry::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_paths(root: &Path, entries: &[VaultEntry]) -> Vec<String> {
|
||||
entries
|
||||
.iter()
|
||||
.map(|entry| relative_path(root, Path::new(&entry.path)).unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filters_ignored_entries_with_git_style_negation() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
init_git_repo(dir.path());
|
||||
write_file(dir.path(), ".gitignore", "ignored/*\n!ignored/keep.md\n");
|
||||
write_file(dir.path(), "visible.md", "# Visible\n");
|
||||
write_file(dir.path(), "ignored/hidden.md", "# Hidden\n");
|
||||
write_file(dir.path(), "ignored/keep.md", "# Keep\n");
|
||||
|
||||
let filtered = filter_gitignored_entries(
|
||||
dir.path(),
|
||||
vec![
|
||||
entry(dir.path(), "visible.md"),
|
||||
entry(dir.path(), "ignored/hidden.md"),
|
||||
entry(dir.path(), "ignored/keep.md"),
|
||||
],
|
||||
true,
|
||||
);
|
||||
assert_eq!(
|
||||
entry_paths(dir.path(), &filtered),
|
||||
vec!["visible.md", "ignored/keep.md"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_ignored_entries_when_visibility_is_enabled() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
init_git_repo(dir.path());
|
||||
write_file(dir.path(), ".gitignore", "ignored/\n");
|
||||
|
||||
let entries = vec![entry(dir.path(), "ignored/hidden.md")];
|
||||
let filtered = filter_gitignored_entries(dir.path(), entries, false);
|
||||
assert_eq!(
|
||||
entry_paths(dir.path(), &filtered),
|
||||
vec!["ignored/hidden.md"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filters_ignored_folder_trees() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
init_git_repo(dir.path());
|
||||
write_file(dir.path(), ".gitignore", "generated/\n");
|
||||
fs::create_dir_all(dir.path().join("generated/nested")).unwrap();
|
||||
fs::create_dir_all(dir.path().join("notes")).unwrap();
|
||||
|
||||
let folders = vec![
|
||||
FolderNode {
|
||||
name: "generated".to_string(),
|
||||
path: "generated".to_string(),
|
||||
children: vec![FolderNode {
|
||||
name: "nested".to_string(),
|
||||
path: "generated/nested".to_string(),
|
||||
children: vec![],
|
||||
}],
|
||||
},
|
||||
FolderNode {
|
||||
name: "notes".to_string(),
|
||||
path: "notes".to_string(),
|
||||
children: vec![],
|
||||
},
|
||||
];
|
||||
|
||||
let filtered = filter_gitignored_folders(dir.path(), folders, true);
|
||||
assert_eq!(filtered.len(), 1);
|
||||
assert_eq!(filtered[0].path, "notes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_no_effect_without_gitignore_file() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
init_git_repo(dir.path());
|
||||
|
||||
let entries = vec![entry(dir.path(), "notes/local.md")];
|
||||
let filtered = filter_gitignored_entries(dir.path(), entries, true);
|
||||
assert_eq!(entry_paths(dir.path(), &filtered), vec!["notes/local.md"]);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ pub(crate) mod filename_rules;
|
||||
mod folders;
|
||||
mod frontmatter;
|
||||
mod getting_started;
|
||||
mod ignored;
|
||||
mod image;
|
||||
mod migration;
|
||||
mod parsing;
|
||||
@@ -24,6 +25,7 @@ pub use entry::{FolderNode, VaultEntry};
|
||||
pub use file::{create_note_content, get_note_content, save_note_content};
|
||||
pub use folders::{delete_folder, rename_folder, FolderRenameResult};
|
||||
pub use getting_started::{create_getting_started_vault, default_vault_path, vault_exists};
|
||||
pub use ignored::{filter_gitignored_entries, filter_gitignored_folders, filter_gitignored_paths};
|
||||
pub use image::{copy_image_to_vault, save_image};
|
||||
pub use migration::migrate_is_a_to_type;
|
||||
pub use rename::{
|
||||
|
||||
Reference in New Issue
Block a user