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
use std::marker::PhantomData;

use clap::Parser;
use nu_ansi_term::{Color, Style};
use reedline::{
    default_emacs_keybindings, DefaultHinter, DefaultPrompt, EditMode, Emacs, IdeMenu,
    KeyModifiers, MenuBuilder, Prompt, Reedline, ReedlineEvent, ReedlineMenu,
};

use crate::{ClapEditor, ReedCompleter};

pub struct ClapEditorBuilder<C: Parser + Send + Sync + 'static> {
    prompt: Box<dyn Prompt>,
    edit_mode: Box<dyn EditMode>,
    hook: Box<dyn FnOnce(Reedline) -> Reedline>,
    c_phantom: PhantomData<C>,
}

impl<C: Parser + Send + Sync + 'static> ClapEditorBuilder<C> {
    pub(crate) fn new() -> Self {
        Self {
            prompt: Box::<DefaultPrompt>::default(),
            edit_mode: {
                let mut keybindings = default_emacs_keybindings();
                keybindings.add_binding(
                    KeyModifiers::NONE,
                    reedline::KeyCode::Tab,
                    ReedlineEvent::UntilFound(vec![
                        ReedlineEvent::Menu("completion_menu".to_string()),
                        ReedlineEvent::MenuNext,
                    ]),
                );
                Box::new(Emacs::new(keybindings))
            },
            hook: Box::new(|e| e),
            c_phantom: PhantomData,
        }
    }

    pub fn with_prompt(mut self, prompt: Box<dyn Prompt>) -> Self {
        self.prompt = prompt;
        self
    }

    pub fn with_edit_mode(mut self, edit_mode: Box<dyn EditMode>) -> Self {
        self.edit_mode = edit_mode;
        self
    }

    pub fn with_editor_hook(mut self, hook: impl FnOnce(Reedline) -> Reedline + 'static) -> Self {
        self.hook = Box::new(hook);
        self
    }

    pub fn build(self) -> ClapEditor<C> {
        let completion_menu = Box::new(
            IdeMenu::default()
                .with_default_border()
                .with_name("completion_menu"),
        );

        let rl = Reedline::create()
            .with_completer(Box::new(ReedCompleter::<C> {
                c_phantom: PhantomData,
            }))
            .with_menu(ReedlineMenu::EngineCompleter(completion_menu))
            .with_hinter(Box::new(
                DefaultHinter::default().with_style(Style::new().italic().fg(Color::DarkGray)),
            ))
            .with_edit_mode(self.edit_mode);
        let rl = (self.hook)(rl);
        ClapEditor {
            rl,
            prompt: self.prompt,
            c_phantom: PhantomData,
        }
    }
}