gn_cli/commands/
picker.rs1use anyhow::Result;
2use crossterm::{
3 cursor,
4 event::{self, Event, KeyCode},
5 execute,
6 terminal::{self, ClearType},
7};
8use gn_core::note::Note;
9use std::io::{stdout, Write};
10
11pub fn pick_note<'a>(title: &str, notes: &'a [Note]) -> Result<Option<&'a Note>> {
14 if notes.is_empty() {
15 return Ok(None);
16 }
17 if notes.len() == 1 {
18 return Ok(Some(¬es[0]));
19 }
20
21 terminal::enable_raw_mode()?;
22 let mut stdout = stdout();
23 execute!(stdout, cursor::Hide)?;
24
25 let mut selected = notes.len() - 1; let render = |stdout: &mut std::io::Stdout, sel: usize| -> Result<()> {
28 execute!(stdout, cursor::MoveToColumn(0), terminal::Clear(ClearType::FromCursorDown))?;
29 println!("\x1b[1;36m? {}\x1b[0m \x1b[90m(Use ↑/↓ arrows, Enter to select, Esc to cancel)\x1b[0m\r", title);
30
31 let start = sel.saturating_sub(5);
32 let end = (start + 8).min(notes.len());
33
34 for (i, n) in notes.iter().enumerate().take(end).skip(start) {
35 let id_short = &n.id.to_string()[..8];
36 let file = n.file.as_deref().unwrap_or("-");
37 let line = n.line_start.unwrap_or(0);
38 let author = n.author.split('<').next().unwrap_or(&n.author).trim();
39 let body_preview = n.body.replace('\n', " ");
40 let body_short = if body_preview.len() > 38 {
41 format!("{}...", &body_preview[..35])
42 } else {
43 body_preview
44 };
45
46 if i == sel {
47 println!(
48 " \x1b[36m❯ [{}] {:<8} {:<18} {:<12} \"{}\"\x1b[0m\r",
49 i + 1,
50 id_short,
51 format!("{}:{}", file, line),
52 author,
53 body_short
54 );
55 } else {
56 println!(
57 " [{}] {:<8} {:<18} {:<12} \"{}\"\r",
58 i + 1,
59 id_short,
60 format!("{}:{}", file, line),
61 author,
62 body_short
63 );
64 }
65 }
66 stdout.flush()?;
67 Ok(())
68 };
69
70 render(&mut stdout, selected)?;
71
72 let result = loop {
73 if let Event::Key(key) = event::read()? {
74 match key.code {
75 KeyCode::Up => {
76 if selected > 0 {
77 selected -= 1;
78 let lines_printed = ((selected + 1 - selected.saturating_sub(5)) + 2) as u16;
80 execute!(stdout, cursor::MoveUp(lines_printed.min(10)))?;
81 render(&mut stdout, selected)?;
82 }
83 }
84 KeyCode::Down => {
85 if selected + 1 < notes.len() {
86 selected += 1;
87 let lines_printed = ((selected - selected.saturating_sub(5)) + 2) as u16;
88 execute!(stdout, cursor::MoveUp(lines_printed.min(10)))?;
89 render(&mut stdout, selected)?;
90 }
91 }
92 KeyCode::Enter => {
93 break Ok(Some(¬es[selected]));
94 }
95 KeyCode::Esc | KeyCode::Char('q') => {
96 break Ok(None);
97 }
98 _ => {}
99 }
100 }
101 };
102
103 execute!(stdout, cursor::Show)?;
104 terminal::disable_raw_mode()?;
105 println!();
106 result
107}