Speed up note switcher and sync window scaling

This commit is contained in:
Yuri Karamian
2026-08-07 16:51:06 +02:00
parent 7ca226d918
commit 73f7604187
7 changed files with 248 additions and 18 deletions
+42 -6
View File
@@ -462,11 +462,19 @@ pub fn set_line_height(state: State<'_, AppState>, height: f64) -> Result<(), St
}
#[tauri::command]
pub fn set_ui_scale(state: State<'_, AppState>, scale: f64) -> Result<(), String> {
pub fn set_ui_scale(
app: AppHandle,
state: State<'_, AppState>,
scale: f64,
) -> Result<(), String> {
let mut config = state.config.lock().map_err(|e| e.to_string())?;
config.ui_scale = Some(scale);
save_app_config(&config)?;
Ok(())
drop(config);
use tauri::Emitter;
app.emit("ui-scale-changed", scale)
.map_err(|error| error.to_string())
}
#[tauri::command]
@@ -799,6 +807,26 @@ pub fn get_all_note_titles(state: State<'_, AppState>) -> Result<Vec<NoteTitleEn
Ok(entries)
}
#[tauri::command]
pub async fn get_note_switcher_titles(
state: State<'_, AppState>,
recent_paths: Vec<String>,
) -> Result<Vec<NoteTitleEntry>, String> {
let vault_path = state
.config
.lock()
.map_err(|error| error.to_string())?
.active_vault
.clone()
.ok_or_else(|| "No active vault".to_string())?;
tauri::async_runtime::spawn_blocking(move || {
operations::get_note_switcher_titles(&vault_path, &recent_paths)
})
.await
.map_err(|error| error.to_string())?
}
// ── Graph ──
#[tauri::command]
@@ -1502,10 +1530,18 @@ pub fn set_general_settings(
// ── Quick Access ──
#[tauri::command]
pub fn get_quick_access(state: State<'_, AppState>) -> Result<Vec<NoteEntry>, String> {
let config = state.config.lock().map_err(|e| e.to_string())?;
let vault_path = config.active_vault.as_ref().ok_or("No active vault")?;
operations::get_quick_access_notes(vault_path)
pub async fn get_quick_access(state: State<'_, AppState>) -> Result<Vec<NoteEntry>, String> {
let vault_path = state
.config
.lock()
.map_err(|error| error.to_string())?
.active_vault
.clone()
.ok_or_else(|| "No active vault".to_string())?;
tauri::async_runtime::spawn_blocking(move || operations::get_quick_access_notes(&vault_path))
.await
.map_err(|error| error.to_string())?
}
#[tauri::command]
+1
View File
@@ -158,6 +158,7 @@ pub fn run() {
commands::move_note,
commands::get_all_tags,
commands::get_all_note_titles,
commands::get_note_switcher_titles,
commands::get_graph_data,
commands::get_tasks,
commands::set_task_done,
+128 -4
View File
@@ -1,10 +1,14 @@
use crate::types::{NoteContent, NoteEntry, NoteMeta, NotebookEntry, TrashContents, TrashNotebookEntry, VaultState};
use crate::types::{
NoteContent, NoteEntry, NoteMeta, NoteTitleEntry, NotebookEntry, TrashContents,
TrashNotebookEntry, VaultState,
};
use crate::vault::frontmatter;
use chrono::{DateTime, Local, Locale, Utc};
use rayon::prelude::*;
use std::collections::HashSet;
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::path::{Component, Path, PathBuf};
use uuid::Uuid;
use walkdir::WalkDir;
@@ -1279,6 +1283,59 @@ pub fn remove_quick_access(vault_path: &str, note_relative: &str) -> Result<(),
Ok(())
}
const NOTE_SWITCHER_RECENT_LIMIT: usize = 6;
pub fn get_note_switcher_titles(
vault_path: &str,
recent_paths: &[String],
) -> Result<Vec<NoteTitleEntry>, String> {
let vault_root = Path::new(vault_path);
if !vault_root.is_dir() {
return Err("Vault path does not exist".to_string());
}
let mut seen = HashSet::new();
let mut titles = Vec::with_capacity(NOTE_SWITCHER_RECENT_LIMIT);
for requested_path in recent_paths {
if titles.len() >= NOTE_SWITCHER_RECENT_LIMIT {
break;
}
let path = Path::new(requested_path);
let Ok(relative) = path.strip_prefix(vault_root) else {
continue;
};
let safe_relative = !relative.as_os_str().is_empty()
&& relative.components().all(|component| match component {
Component::Normal(name) => !is_hidden(Path::new(name)),
Component::CurDir => true,
_ => false,
});
if !safe_relative
|| path.extension().and_then(|extension| extension.to_str()) != Some("md")
|| !path.is_file()
{
continue;
}
let relative_path = relative.to_path_buf();
if !seen.insert(relative_path.clone()) {
continue;
}
let Ok(entry) = read_note_entry_fast(path, vault_root) else {
continue;
};
titles.push(NoteTitleEntry {
title: entry.meta.title,
path: relative_path.to_string_lossy().replace('\\', "/"),
});
}
Ok(titles)
}
pub fn get_quick_access_notes(vault_path: &str) -> Result<Vec<NoteEntry>, String> {
let list = load_quick_access(vault_path)?;
let vault_root = Path::new(vault_path);
@@ -1287,7 +1344,7 @@ pub fn get_quick_access_notes(vault_path: &str) -> Result<Vec<NoteEntry>, String
for relative in &list {
let full_path = vault_root.join(relative);
if full_path.exists() {
if let Ok(entry) = read_note_entry(&full_path, vault_root) {
if let Ok(entry) = read_note_entry_fast(&full_path, vault_root) {
notes.push(entry);
}
}
@@ -1309,7 +1366,10 @@ pub fn sanitize_filename(name: &str) -> String {
#[cfg(test)]
mod tests {
use super::{duplicate_note, helixnotes_dir, load_notebook_icons, set_notebook_icon};
use super::{
duplicate_note, get_note_switcher_titles, helixnotes_dir, load_notebook_icons,
set_notebook_icon,
};
use std::fs;
use uuid::Uuid;
@@ -1333,6 +1393,70 @@ mod tests {
fs::remove_dir_all(vault).unwrap();
}
#[test]
fn loads_only_requested_note_switcher_titles() {
let vault =
std::env::temp_dir().join(format!("helixnotes-note-switcher-test-{}", Uuid::new_v4()));
fs::create_dir_all(&vault).unwrap();
let mut note_paths = Vec::new();
for index in 0..8 {
let path = vault.join(format!("Note {index}.md"));
fs::write(
&path,
format!("---\ntitle: Note {index}\n---\n\nNote {index} body.\n"),
)
.unwrap();
note_paths.push(path);
}
fs::write(
vault.join("Unrelated.md"),
"---\ntitle: Unrelated\n---\n\nMust not be loaded.\n",
)
.unwrap();
let outside =
std::env::temp_dir().join(format!("helixnotes-outside-note-{}.md", Uuid::new_v4()));
fs::write(&outside, "---\ntitle: Outside\n---\n").unwrap();
let requested = vec![
note_paths[0].to_string_lossy().into_owned(),
note_paths[0].to_string_lossy().into_owned(),
vault.join("Missing.md").to_string_lossy().into_owned(),
outside.to_string_lossy().into_owned(),
note_paths[1].to_string_lossy().into_owned(),
note_paths[2].to_string_lossy().into_owned(),
note_paths[3].to_string_lossy().into_owned(),
note_paths[4].to_string_lossy().into_owned(),
note_paths[5].to_string_lossy().into_owned(),
note_paths[6].to_string_lossy().into_owned(),
note_paths[7].to_string_lossy().into_owned(),
];
let vault_path = vault.to_string_lossy();
let titles = get_note_switcher_titles(&vault_path, &requested).unwrap();
assert_eq!(titles.len(), 6);
assert_eq!(
titles
.iter()
.map(|entry| entry.path.as_str())
.collect::<Vec<_>>(),
vec![
"Note 0.md",
"Note 1.md",
"Note 2.md",
"Note 3.md",
"Note 4.md",
"Note 5.md",
]
);
assert!(titles.iter().all(|entry| entry.title != "Unrelated"));
assert!(titles.iter().all(|entry| entry.title != "Outside"));
fs::remove_dir_all(vault).unwrap();
fs::remove_file(outside).unwrap();
}
#[test]
fn duplicates_note_content_and_assigns_unique_identity_and_name() {
let vault =