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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! Shell runtime hooks
//!
//! Hooks are user provided functions that are called on a variety of events that occur in the
//! shell. Some additional context is provided to these hooks.
// ideas for hooks
// - on start
// - after prompt
// - before prompt
// - internal error hook (call whenever there is internal shell error; good for debug)
// - env hook (when environment variable is set/changed)
// - exit hook (tricky, make sure we know what cases to call this)

use std::{
    any::{Any, TypeId},
    collections::HashMap,
    io::BufWriter,
    marker::PhantomData,
    path::PathBuf,
    process::ExitStatus,
    time::Duration,
};

use crossterm::{style::Print, QueueableCommand};

use crate::{
    cmd_output::CmdOutput,
    shell::{Context, Runtime, Shell},
};

pub type HookFn<C: Clone> =
    fn(sh: &Shell, sh_ctx: &mut Context, sh_rt: &mut Runtime, ctx: &C) -> anyhow::Result<()>;

// TODO this is some pretty sus implementation
pub trait Hook<C>: FnMut(&Shell, &mut Context, &mut Runtime, &C) -> anyhow::Result<()> {}

impl<C, T: FnMut(&Shell, &mut Context, &mut Runtime, &C) -> anyhow::Result<()>> Hook<C> for T {}

/// Runs when the shell starts up
#[derive(Clone)]
pub struct StartupCtx {
    /// How long it took the shell to startup
    pub startup_time: Duration,
}

/// Default implementation for [StartupCtx]
pub fn startup_hook(
    sh: &Shell,
    sh_ctx: &mut Context,
    sh_rt: &mut Runtime,
    _ctx: &StartupCtx,
) -> anyhow::Result<()> {
    println!("welcome to shrs!");
    Ok(())
}

/// Runs before a command is executed
#[derive(Clone)]
pub struct BeforeCommandCtx {
    /// Literal command entered by user
    pub raw_command: String,
    /// Command to be executed, after performing all substitutions
    pub command: String,
}
/// Default implementation for [BeforeCommandCtx]
pub fn before_command_hook(
    sh: &Shell,
    sh_ctx: &mut Context,
    sh_rt: &mut Runtime,
    ctx: &BeforeCommandCtx,
) -> anyhow::Result<()> {
    // let expanded_cmd = format!("[evaluating] {}\n", ctx.command);
    // out.queue(Print(expanded_cmd))?;
    Ok(())
}

/// Runs after a command is executed
#[derive(Clone)]
pub struct AfterCommandCtx {
    /// The command that was ran
    pub command: String,
    /// Command output
    pub cmd_output: CmdOutput,
}

/// Default implementation for [AfterCommandCtx]
pub fn after_command_hook(
    sh: &Shell,
    sh_ctx: &mut Context,
    sh_rt: &mut Runtime,
    ctx: &AfterCommandCtx,
) -> anyhow::Result<()> {
    // let exit_code_str = format!("[exit +{}]\n", ctx.exit_code);
    // out.queue(Print(exit_code_str))?;
    Ok(())
}

/// Runs when the current working directory is modified
#[derive(Clone)]
pub struct ChangeDirCtx {
    pub old_dir: PathBuf,
    pub new_dir: PathBuf,
}

/// Default implementation for [ChangeDirCtx]
pub fn change_dir_hook(
    sh: &Shell,
    sh_ctx: &mut Context,
    sh_rt: &mut Runtime,
    ctx: &ChangeDirCtx,
) -> anyhow::Result<()> {
    Ok(())
}

/// Runs when a job is completed
#[derive(Clone)]
pub struct JobExitCtx {
    pub status: ExitStatus,
}

/// Default implementation for [JobExitCtx]
pub fn job_exit_hook(
    sh: &Shell,
    sh_ctx: &mut Context,
    sh_rt: &mut Runtime,
    ctx: &JobExitCtx,
) -> anyhow::Result<()> {
    println!("[exit +{:?}]", ctx.status.code());
    Ok(())
}

// /// Hook that runs when a command has a specific exit code
// #[derive(Clone)]
// pub struct ExitStatusCtx<const C: i32> { }

/// Collection of all the hooks that are available
pub struct Hooks {
    // TODO how to uniquely identify a hook? using the Ctx type?
    hooks: anymap::Map,
}

impl Default for Hooks {
    /// Register default hooks
    fn default() -> Self {
        let mut hooks = Hooks::new();

        hooks.register(startup_hook);
        hooks.register(before_command_hook);
        hooks.register(after_command_hook);
        hooks.register(change_dir_hook);
        hooks.register(job_exit_hook);

        hooks
    }
}

impl Hooks {
    pub fn new() -> Self {
        Self {
            hooks: anymap::Map::new(),
        }
    }

    /// Registers a new hook
    pub fn register<C: Clone + 'static>(&mut self, hook: HookFn<C>) {
        match self.hooks.get_mut::<Vec<HookFn<C>>>() {
            Some(hook_list) => {
                hook_list.push(hook);
            },
            None => {
                // register any empty vector for the type
                self.hooks.insert::<Vec<HookFn<C>>>(vec![hook]);
            },
        };
    }

    /*
    /// Register from an iterator
    pub fn register_iter(&mut self) {
        unimplemented!()
    }
    */

    /// Executes all registered hooks
    pub fn run<C: Clone + 'static>(
        &self,
        sh: &Shell,
        sh_ctx: &mut Context,
        sh_rt: &mut Runtime,
        ctx: C,
    ) -> anyhow::Result<()> {
        if let Some(hook_list) = self.hooks.get::<Vec<HookFn<C>>>() {
            for hook in hook_list.iter() {
                (hook)(sh, sh_ctx, sh_rt, &ctx)?;
            }
        }
        Ok(())
    }
}