-
Notifications
You must be signed in to change notification settings - Fork 622
/
Copy pathquery.rs
139 lines (124 loc) · 4.51 KB
/
query.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use std::io::{self, Write};
use anyhow::{Context, Result};
use crate::cmd::{Query, Run};
use crate::config;
use crate::db::{Database, Epoch, Stream, StreamOptions};
use crate::error::BrokenPipeHandler;
use crate::util::{self, Fzf, FzfChild};
impl Run for Query {
fn run(&self) -> Result<()> {
let mut db = crate::db::Database::open()?;
self.query(&mut db).and(db.save())
}
}
impl Query {
fn query(&self, db: &mut Database) -> Result<()> {
let now = util::current_time()?;
let mut stream = self.get_stream(db, now)?;
if self.interactive {
self.query_interactive(&mut stream, now)
} else if self.list {
self.query_list(&mut stream, now)
} else {
self.query_first(&mut stream, now)
}
}
fn query_interactive(&self, stream: &mut Stream, now: Epoch) -> Result<()> {
let mut fzf = Self::get_fzf()?;
let selection = loop {
match stream.next() {
Some(dir) if Some(dir.path.as_ref()) == self.exclude.as_deref() => continue,
Some(dir) => {
if let Some(selection) = fzf.write(dir, now)? {
break selection;
}
}
None => break fzf.wait()?,
}
};
if self.score {
print!("{selection}");
} else {
let path = selection.get(7..).context("could not read selection from fzf")?;
print!("{path}");
}
Ok(())
}
fn query_list(&self, stream: &mut Stream, now: Epoch) -> Result<()> {
let handle = &mut io::stdout().lock();
while let Some(dir) = stream.next() {
if Some(dir.path.as_ref()) == self.exclude.as_deref() {
continue;
}
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
writeln!(handle, "{dir}").pipe_exit("stdout")?;
}
Ok(())
}
fn query_first(&self, stream: &mut Stream, now: Epoch) -> Result<()> {
let handle = &mut io::stdout();
let mut dir = stream.next().context("no match found")?;
while Some(dir.path.as_ref()) == self.exclude.as_deref() {
dir = stream.next().context("you are already in the only match")?;
}
let dir = if self.score { dir.display().with_score(now) } else { dir.display() };
writeln!(handle, "{dir}").pipe_exit("stdout")
}
fn get_stream<'a>(&self, db: &'a mut Database, now: Epoch) -> Result<Stream<'a>> {
let mut options = StreamOptions::new(now)
.with_keywords(self.transformed_keywords())
.with_exclude(config::exclude_dirs()?);
if !self.all {
let resolve_symlinks = config::resolve_symlinks();
options = options.with_exists(true).with_resolve_symlinks(resolve_symlinks);
}
let stream = Stream::new(db, options);
Ok(stream)
}
fn get_fzf() -> Result<FzfChild> {
let mut fzf = Fzf::new()?;
if let Some(fzf_opts) = config::fzf_opts() {
fzf.env("FZF_DEFAULT_OPTS", fzf_opts)
} else {
fzf.args([
// Search mode
"--exact",
// Search result
"--no-sort",
// Interface
"--bind=ctrl-z:ignore,btab:up,tab:down",
"--cycle",
"--keep-right",
// Layout
"--border=sharp", // rounded edges don't display correctly on some terminals
"--height=45%",
"--info=inline",
"--layout=reverse",
// Display
"--tabstop=1",
// Scripting
"--exit-0",
])
.enable_preview()
}
.spawn()
}
/// ## Returns
/// `self.keywords` with file paths transformed into
/// their parent directory paths, directory paths unchanged
///
/// ## Notes
/// - Heap allocates an iterator of Strings with length of self.keywords
/// - Only uses sans-IO Path methods
fn transformed_keywords(&self) -> impl Iterator<Item = String> + use<'_> {
self.keywords.iter().map(|keyword| {
let path = std::path::Path::new(keyword);
match path.parent() {
None => keyword,
Some(path) if path.to_str() == Some("") => keyword,
Some(path) => path.to_str().unwrap(),
}
.to_string()
})
}
}