intelli_shell/utils/
history.rs1use std::{fs, io::ErrorKind, process::Command};
2
3use color_eyre::eyre::{Context, Report};
4use directories::BaseDirs;
5
6use crate::{
7 cli::HistorySource,
8 errors::{Result, UserFacingError},
9};
10
11pub fn read_history(source: HistorySource) -> Result<String> {
16 match source {
17 HistorySource::Bash => read_bash_history(),
18 HistorySource::Zsh => read_zsh_history(),
19 HistorySource::Fish => read_fish_history(),
20 HistorySource::Powershell => read_powershell_history(),
21 HistorySource::Atuin => read_atuin_history(),
22 }
23}
24
25fn read_bash_history() -> Result<String> {
26 read_history_from_home(&[".bash_history"])
27}
28
29fn read_zsh_history() -> Result<String> {
30 read_history_from_home(&[".zsh_history"])
31}
32
33fn read_fish_history() -> Result<String> {
34 read_history_from_home(&[".local", "share", "fish", "fish_history"])
35}
36
37fn read_powershell_history() -> Result<String> {
38 let path = if cfg!(windows) {
39 vec![
40 "AppData",
41 "Roaming",
42 "Microsoft",
43 "Windows",
44 "PowerShell",
45 "PSReadLine",
46 "ConsoleHost_history.txt",
47 ]
48 } else {
49 vec![".local", "share", "powershell", "PSReadLine", "ConsoleHost_history.txt"]
50 };
51 read_history_from_home(&path)
52}
53
54fn read_atuin_history() -> Result<String> {
55 let output = Command::new("atuin")
57 .arg("history")
58 .arg("list")
59 .arg("--cmd-only")
60 .output();
61
62 match output {
63 Ok(output) if output.status.success() => {
64 Ok(String::from_utf8(output.stdout).wrap_err("Couldn't read atuin output")?)
65 }
66 Ok(output) => {
67 let stderr = String::from_utf8_lossy(&output.stderr);
68 if !stderr.is_empty() {
69 tracing::error!("Couldn't execute atuin: {stderr}");
70 }
71 Err(UserFacingError::HistoryAtuinFailed.into())
72 }
73 Err(err) if err.kind() == ErrorKind::NotFound => Err(UserFacingError::HistoryAtuinNotFound.into()),
74 Err(err) => Err(Report::from(err).wrap_err("Couldn't run atuin").into()),
75 }
76}
77
78fn read_history_from_home(path_segments: &[&str]) -> Result<String> {
80 let mut path = BaseDirs::new()
81 .ok_or(UserFacingError::HistoryHomeDirNotFound)?
82 .home_dir()
83 .to_path_buf();
84 for segment in path_segments {
85 path.push(segment);
86 }
87 fs::read_to_string(&path).map_err(|err| {
88 if err.kind() == ErrorKind::NotFound {
89 UserFacingError::HistoryFileNotFound(path.to_string_lossy().into_owned()).into()
90 } else if err.kind() == ErrorKind::PermissionDenied {
91 UserFacingError::FileNotAccessible("read").into()
92 } else {
93 Report::from(err)
94 .wrap_err(format!("Couldn't read history file {}", path.display()))
95 .into()
96 }
97 })
98}