feat(quick-1): add arrow key navigation and directory descriptions
- Add bare Left arrow key as alias for navigate_back (same as Backspace) - Add bare Right arrow key as alias for navigate_forward (same as Alt+Right) - Update handle_key doc comment to list new Left/Right bindings - Add description field to DirEntry struct in vault.rs - Add extract_frontmatter_description() to parse YAML frontmatter - Populate description from frontmatter in list_vault_files() - Show descriptions in DarkGray beside file entries in build_directory_lines() - Truncate descriptions to keep total line width within 78 chars - Link span_len covers only [name] portion, not the description
This commit is contained in:
+42
-6
@@ -450,6 +450,8 @@ impl App {
|
|||||||
/// - `Backspace` — navigate back in history
|
/// - `Backspace` — navigate back in history
|
||||||
/// - `Alt+Left` — navigate back in history
|
/// - `Alt+Left` — navigate back in history
|
||||||
/// - `Alt+Right` — navigate forward in history
|
/// - `Alt+Right` — navigate forward in history
|
||||||
|
/// - `Left` — navigate back in history (same as Backspace)
|
||||||
|
/// - `Right` — navigate forward in history (same as Alt+Right)
|
||||||
/// - `j` / `Down` — scroll down one line
|
/// - `j` / `Down` — scroll down one line
|
||||||
/// - `k` / `Up` — scroll up one line
|
/// - `k` / `Up` — scroll up one line
|
||||||
/// - `PgDn` — scroll down one page
|
/// - `PgDn` — scroll down one page
|
||||||
@@ -498,6 +500,13 @@ impl App {
|
|||||||
KeyCode::Right if key.modifiers.contains(KeyModifiers::ALT) => {
|
KeyCode::Right if key.modifiers.contains(KeyModifiers::ALT) => {
|
||||||
self.navigate_forward();
|
self.navigate_forward();
|
||||||
}
|
}
|
||||||
|
// Bare Left/Right arrows also navigate back/forward
|
||||||
|
KeyCode::Left => {
|
||||||
|
self.navigate_back();
|
||||||
|
}
|
||||||
|
KeyCode::Right => {
|
||||||
|
self.navigate_forward();
|
||||||
|
}
|
||||||
// ── Scrolling keys — do NOT dismiss the quit prompt ───────────────
|
// ── Scrolling keys — do NOT dismiss the quit prompt ───────────────
|
||||||
KeyCode::Char('j') | KeyCode::Down => {
|
KeyCode::Char('j') | KeyCode::Down => {
|
||||||
self.scroll_down(1);
|
self.scroll_down(1);
|
||||||
@@ -1362,13 +1371,40 @@ fn build_directory_lines(
|
|||||||
let span_len = display.chars().count();
|
let span_len = display.chars().count();
|
||||||
let line_index = lines.len(); // This line's index in the lines vec
|
let line_index = lines.len(); // This line's index in the lines vec
|
||||||
|
|
||||||
lines.push(Line::from(vec![
|
// Build spans: indent + [name] + optional description in DarkGray
|
||||||
|
let mut spans = vec![
|
||||||
Span::raw(indent),
|
Span::raw(indent),
|
||||||
Span::styled(
|
Span::styled(display.clone(), Style::default().fg(Color::LightCyan)),
|
||||||
display,
|
];
|
||||||
Style::default().fg(Color::LightCyan),
|
|
||||||
),
|
if let Some(ref desc) = entry.description {
|
||||||
]));
|
// Truncate description so total line width stays within ~78 chars
|
||||||
|
// Total used: indent_chars + span_len + 2 (separator) + desc
|
||||||
|
let budget = 78usize
|
||||||
|
.saturating_sub(indent_chars)
|
||||||
|
.saturating_sub(span_len)
|
||||||
|
.saturating_sub(2); // 2 spaces separator
|
||||||
|
let truncated = if desc.chars().count() > budget {
|
||||||
|
if budget > 3 {
|
||||||
|
let trimmed: String = desc.chars().take(budget - 3).collect();
|
||||||
|
format!("{}...", trimmed)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
desc.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
if !truncated.is_empty() {
|
||||||
|
spans.push(Span::raw(" "));
|
||||||
|
spans.push(Span::styled(
|
||||||
|
truncated,
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(Line::from(spans));
|
||||||
|
|
||||||
link_records.push(crate::renderer::LinkRecord {
|
link_records.push(crate::renderer::LinkRecord {
|
||||||
line_index,
|
line_index,
|
||||||
|
|||||||
+60
-1
@@ -1,4 +1,4 @@
|
|||||||
use std::io;
|
use std::io::{self, BufRead};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
@@ -201,6 +201,62 @@ pub struct DirEntry {
|
|||||||
pub is_dir: bool,
|
pub is_dir: bool,
|
||||||
/// Vault-relative path (only for .md files; None for directories).
|
/// Vault-relative path (only for .md files; None for directories).
|
||||||
pub vault_path: Option<String>,
|
pub vault_path: Option<String>,
|
||||||
|
/// Short description from the file's YAML frontmatter `description:` field.
|
||||||
|
/// None if the file has no frontmatter or no description key.
|
||||||
|
pub description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Frontmatter parsing ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Extract the `description` value from a YAML frontmatter block at the top of a file.
|
||||||
|
///
|
||||||
|
/// Reads only the first 20 lines to avoid loading large files entirely.
|
||||||
|
/// The frontmatter must start with `---` on the first line and be closed by
|
||||||
|
/// another `---` line. The `description:` key is matched case-sensitively.
|
||||||
|
///
|
||||||
|
/// Handles both bare values (`description: some text`) and quoted values
|
||||||
|
/// (`description: "some text"`).
|
||||||
|
///
|
||||||
|
/// Returns `Some(description)` if found, `None` otherwise.
|
||||||
|
fn extract_frontmatter_description(path: &Path) -> Option<String> {
|
||||||
|
let file = std::fs::File::open(path).ok()?;
|
||||||
|
let reader = io::BufReader::new(file);
|
||||||
|
|
||||||
|
let mut lines = reader.lines().take(20);
|
||||||
|
|
||||||
|
// First line must be exactly "---"
|
||||||
|
let first = lines.next()?.ok()?;
|
||||||
|
if first.trim() != "---" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan remaining lines for description key, stop at closing "---"
|
||||||
|
for line in lines {
|
||||||
|
let line = line.ok()?;
|
||||||
|
let trimmed = line.trim();
|
||||||
|
|
||||||
|
if trimmed == "---" {
|
||||||
|
// End of frontmatter — description not found
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rest) = trimmed.strip_prefix("description:") {
|
||||||
|
let value = rest.trim();
|
||||||
|
// Strip surrounding quotes if present
|
||||||
|
let value = if (value.starts_with('"') && value.ends_with('"'))
|
||||||
|
|| (value.starts_with('\'') && value.ends_with('\''))
|
||||||
|
{
|
||||||
|
&value[1..value.len() - 1]
|
||||||
|
} else {
|
||||||
|
value
|
||||||
|
};
|
||||||
|
if !value.is_empty() {
|
||||||
|
return Some(value.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List all markdown files and directories in the vault, sorted alphabetically.
|
/// List all markdown files and directories in the vault, sorted alphabetically.
|
||||||
@@ -231,6 +287,7 @@ pub fn list_vault_files(vault_path: &Path) -> Vec<DirEntry> {
|
|||||||
name,
|
name,
|
||||||
is_dir: true,
|
is_dir: true,
|
||||||
vault_path: None,
|
vault_path: None,
|
||||||
|
description: None,
|
||||||
});
|
});
|
||||||
} else if name.ends_with(".md") {
|
} else if name.ends_with(".md") {
|
||||||
let display_name = name.strip_suffix(".md").unwrap_or(&name).to_string();
|
let display_name = name.strip_suffix(".md").unwrap_or(&name).to_string();
|
||||||
@@ -240,11 +297,13 @@ pub fn list_vault_files(vault_path: &Path) -> Vec<DirEntry> {
|
|||||||
.unwrap_or(entry.path())
|
.unwrap_or(entry.path())
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
let description = extract_frontmatter_description(entry.path());
|
||||||
entries.push(DirEntry {
|
entries.push(DirEntry {
|
||||||
depth,
|
depth,
|
||||||
name: display_name,
|
name: display_name,
|
||||||
is_dir: false,
|
is_dir: false,
|
||||||
vault_path: Some(rel),
|
vault_path: Some(rel),
|
||||||
|
description,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Skip non-.md files silently
|
// Skip non-.md files silently
|
||||||
|
|||||||
Reference in New Issue
Block a user