duat_core/widgets/
prompt_line.rs

1//! A multi-purpose widget
2//!
3//! This widget serves many purposes, of note is [running commands],
4//! but it can also be used for [incremental search],
5//! [piping selections], and even [user made functionalities].
6//!
7//! These functionalities are all controled by a [`Mode`] that has
8//! control over the [`Text`] in the [`PromptLine`]. By default, that
9//! is the [`Prompt`] mode, which can take in a [`PromptMode`] in
10//! order to determine how the [`Text`] will be interpreted.
11//!
12//! [running commands]: crate::mode::RunCommands
13//! [incremental search]: crate::mode::IncSearch
14//! [piping selections]: crate::mode::PipeSelections
15//! [user made functionalities]: PromptMode
16//! [`Mode`]: crate::mode::Mode
17use std::{any::TypeId, collections::HashMap, marker::PhantomData};
18
19use crate::{
20    cfg::PrintCfg,
21    form, hooks,
22    mode::PromptMode,
23    text::Text,
24    ui::{PushSpecs, Ui},
25    widgets::{Widget, WidgetCfg},
26};
27
28impl<U: Ui> WidgetCfg<U> for PromptLineCfg<U> {
29    type Widget = PromptLine<U>;
30
31    fn build(self, _: bool) -> (Self::Widget, impl Fn() -> bool, PushSpecs) {
32        let specs = if hooks::group_exists("HidePromptLine") {
33            self.specs.with_ver_len(0.0)
34        } else {
35            self.specs
36        };
37
38        let widget = PromptLine {
39            text: Text::default(),
40            prompts: HashMap::new(),
41            _ghost: PhantomData,
42        };
43
44        (widget, || false, specs)
45    }
46}
47
48/// A multi purpose text [`Widget`]
49///
50/// This [`Widget`] will be used by a [`Prompt`]-type [`Mode`], which
51/// in turn will make use of a [`PromptMode`]. These are "ways of
52/// interpreting the input". In Duat, there are 3 built-in
53/// [`PromptMode`]s:
54///
55/// - [`RunCommands`]: Will interpret the prompt as a Duat command to
56///   be executed.
57/// - [`IncSearch`]: Will read the prompt as a regex, and modify the
58///   active [`File`] according to a given [`IncSearcher`].
59/// - [`PipeSelections`]: Will pass each selection to a shell command,
60///   replacing the selections with the `stdout`.
61///
62/// [`Prompt`]: crate::mode::Prompt
63/// [`Mode`]: crate::mode::Mode
64/// [`RunCommands`]: crate::mode::RunCommands
65/// [`IncSearch`]: crate::mode::IncSearch
66/// [`File`]: super::File
67/// [`IncSearcher`]: crate::mode::IncSearcher
68/// [`PipeSelections`]: crate::mode::PipeSelections
69pub struct PromptLine<U> {
70    text: Text,
71    prompts: HashMap<TypeId, Text>,
72    _ghost: PhantomData<U>,
73}
74
75impl<U: Ui> PromptLine<U> {
76    /// Returns the prompt for a [`PromptMode`] if there is any
77    pub fn prompt_of<M: PromptMode<U>>(&self) -> Option<Text> {
78        self.prompts.get(&TypeId::of::<M>()).cloned()
79    }
80
81    /// Sets the prompt for the given [`PromptMode`]
82    pub fn set_prompt<M: PromptMode<U>>(&mut self, text: Text) {
83        self.prompts.entry(TypeId::of::<M>()).or_insert(text);
84    }
85}
86
87impl<U: Ui> Widget<U> for PromptLine<U> {
88    type Cfg = PromptLineCfg<U>;
89
90    fn cfg() -> Self::Cfg {
91        Self::Cfg {
92            prompts: HashMap::new(),
93            specs: PushSpecs::below().with_ver_len(1.0),
94            _ghost: PhantomData,
95        }
96    }
97
98    fn on_unfocus(&mut self, _area: &<U as Ui>::Area) {
99        self.text = Text::new();
100    }
101
102    fn text(&self) -> &Text {
103        &self.text
104    }
105
106    fn text_mut(&mut self) -> &mut Text {
107        &mut self.text
108    }
109
110    fn print_cfg(&self) -> PrintCfg {
111        PrintCfg::default_for_input().with_forced_scrolloff()
112    }
113
114    fn once() -> Result<(), Text> {
115        form::set_weak("Prompt", "DefaultOk");
116        form::set_weak("Prompt.colon", "AccentOk");
117        Ok(())
118    }
119}
120
121#[doc(hidden)]
122pub struct PromptLineCfg<U> {
123    prompts: HashMap<TypeId, Text>,
124    specs: PushSpecs,
125    _ghost: PhantomData<U>,
126}
127
128impl<U: Ui> PromptLineCfg<U> {
129    /// Changes the default [prompt] for a given [mode]
130    ///
131    /// [prompt]: Text
132    /// [mode]: PromptMode
133    pub fn set_prompt<M: PromptMode<U>>(mut self, prompt: Text) -> Self {
134        self.prompts.insert(TypeId::of::<M>(), prompt);
135        self
136    }
137
138    pub fn above(self) -> Self {
139        Self {
140            specs: PushSpecs::above().with_ver_len(1.0),
141            ..self
142        }
143    }
144
145    /// Pushes this [`PromptLine`] to the left
146    pub fn left_ratioed(self, den: u16, div: u16) -> Self {
147        Self {
148            specs: PushSpecs::left().with_hor_ratio(den, div),
149            ..self
150        }
151    }
152}