-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathhistory.rs
124 lines (116 loc) Β· 3.86 KB
/
history.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
use std::{
env,
error::Error,
fs::File,
io::{BufRead, BufReader, Cursor, Read},
process::Command,
};
use crate::view::View;
#[derive(Debug, Clone)]
pub enum HistoryProvider {
Zsh,
Bash,
Atuin,
Fish,
}
impl HistoryProvider {
pub fn from(provider: &String) -> Self {
match provider.as_str() {
"zsh" => Self::Zsh,
"bash" => Self::Bash,
"atuin" => Self::Atuin,
"fish" => Self::Fish,
_ => {
View::content(&format!(
"Sorry, {} is not supported yet\n\n",
provider.split('/').last().unwrap_or("")
));
std::process::exit(1);
}
}
}
pub fn history_stream(&self) -> Result<Box<dyn Read>, Box<dyn Error>> {
match self {
HistoryProvider::Zsh | HistoryProvider::Bash => {
let history_file_name = match self {
HistoryProvider::Zsh => ".zsh_history",
HistoryProvider::Bash => ".bash_history",
_ => unreachable!(),
};
let file_path = format!("{}/{}", env::var("HOME")?, history_file_name);
Ok(Box::new(File::open(file_path)?))
}
HistoryProvider::Atuin => {
let output = Command::new("atuin")
.args(["history", "list", "--format", "{time};{command}"])
.output()?;
Ok(Box::new(Cursor::new(output.stdout)))
}
HistoryProvider::Fish => {
let output = Command::new("fish")
.arg("-c")
.arg("history -show-time='%s;'")
.output()?;
Ok(Box::new(Cursor::new(output.stdout)))
}
}
}
}
pub struct History {
buff_reader: BufReader<Box<dyn Read>>,
provider: HistoryProvider,
}
impl History {
pub fn from(provider: &HistoryProvider) -> Result<Self, Box<dyn Error>> {
Ok(History {
provider: provider.clone(),
buff_reader: BufReader::new(provider.history_stream()?),
})
}
}
impl Iterator for History {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
match self.provider {
HistoryProvider::Zsh | HistoryProvider::Atuin | HistoryProvider::Fish => {
let mut block = String::new();
let mut buf = vec![];
loop {
self.buff_reader.read_until(b'\n', &mut buf).unwrap();
if buf.is_empty() {
return if block.is_empty() { None } else { Some(block) };
}
let str = String::from_utf8_lossy(&buf).trim_end().to_owned();
block += &str;
if str.is_empty() {
buf.clear();
continue;
}
if str.ends_with('\\') {
block = block.strip_suffix('\\')?.into();
buf.clear();
continue;
}
break Some(block);
}
}
HistoryProvider::Bash => {
let mut block = String::new();
let mut buf = vec![];
loop {
self.buff_reader.read_until(b'\n', &mut buf).unwrap();
if buf.is_empty() {
return None;
}
let str = String::from_utf8_lossy(&buf).to_owned();
block += &str;
if str.is_empty() || str.starts_with('#') {
buf.clear();
continue;
}
break Some(block);
}
}
}
}
}