duat_utils/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
17//! [`Prompt`]: crate::modes::Prompt
18use std::{any::TypeId, collections::HashMap, marker::PhantomData};
19
20use duat_core::prelude::*;
21
22use crate::modes::PromptMode;
23
24/// A multi purpose text [`Widget`]
25///
26/// This [`Widget`] will be used by a [`Prompt`]-type [`Mode`], which
27/// in turn will make use of a [`PromptMode`]. These are "ways of
28/// interpreting the input". In Duat, there are 3 built-in
29/// [`PromptMode`]s:
30///
31/// - [`RunCommands`]: Will interpret the prompt as a Duat command to
32///   be executed.
33/// - [`IncSearch`]: Will read the prompt as a regex, and modify the
34///   active [`File`] according to a given [`IncSearcher`].
35/// - [`PipeSelections`]: Will pass each selection to a shell command,
36///   replacing the selections with the `stdout`.
37///
38/// [`Prompt`]: crate::modes::Prompt
39/// [`Mode`]: duat_core::mode::Mode
40/// [`RunCommands`]: crate::modes::RunCommands
41/// [`IncSearch`]: crate::modes::IncSearch
42/// [`File`]: duat_core::file::File
43/// [`IncSearcher`]: crate::modes::IncSearcher
44/// [`PipeSelections`]: crate::modes::PipeSelections
45pub struct PromptLine<U> {
46    text: Text,
47    prompts: HashMap<TypeId, Text>,
48    _ghost: PhantomData<U>,
49}
50
51impl<U: Ui> PromptLine<U> {
52    /// Returns the prompt for a [`PromptMode`] if there is any
53    pub fn prompt_of<M: PromptMode<U>>(&self) -> Option<Text> {
54        self.prompts.get(&TypeId::of::<M>()).cloned()
55    }
56
57    /// Sets the prompt for the given [`PromptMode`]
58    pub fn set_prompt<M: PromptMode<U>>(&mut self, text: Text) {
59        self.prompts.entry(TypeId::of::<M>()).or_insert(text);
60    }
61}
62
63impl<U: Ui> Widget<U> for PromptLine<U> {
64    type Cfg = PromptLineCfg<U>;
65
66    fn update(pa: &mut Pass, handle: &Handle<Self, U>) {
67        let pl = handle.read(pa);
68        if let Some(main) = pl.text.selections().get_main() {
69            handle
70                .area(pa)
71                .scroll_around_point(&pl.text, main.caret(), pl.print_cfg());
72        }
73    }
74
75    fn needs_update(&self, _: &Pass) -> bool {
76        false
77    }
78
79    fn cfg() -> Self::Cfg {
80        Self::Cfg {
81            prompts: HashMap::new(),
82            specs: PushSpecs::below().ver_len(1.0),
83            _ghost: PhantomData,
84        }
85    }
86
87    fn text(&self) -> &Text {
88        &self.text
89    }
90
91    fn text_mut(&mut self) -> &mut Text {
92        &mut self.text
93    }
94
95    fn print_cfg(&self) -> PrintCfg {
96        *PrintCfg::default_for_input().set_forced_horizontal_scrolloff(true)
97    }
98
99    fn once() -> Result<(), Text> {
100        Ok(())
101    }
102}
103
104impl<U: Ui> WidgetCfg<U> for PromptLineCfg<U> {
105    type Widget = PromptLine<U>;
106
107    fn build(self, _: &mut Pass, _: BuildInfo<U>) -> (Self::Widget, PushSpecs) {
108        let specs = if hook::group_exists("HidePromptLine") {
109            self.specs.ver_len(0.0)
110        } else {
111            self.specs
112        };
113
114        let widget = PromptLine {
115            text: Text::default(),
116            prompts: HashMap::new(),
117            _ghost: PhantomData,
118        };
119
120        (widget, specs)
121    }
122}
123
124#[doc(hidden)]
125pub struct PromptLineCfg<U> {
126    prompts: HashMap<TypeId, Text>,
127    specs: PushSpecs,
128    _ghost: PhantomData<U>,
129}
130
131impl<U: Ui> PromptLineCfg<U> {
132    /// Changes the default [prompt] for a given [mode]
133    ///
134    /// [prompt]: Text
135    /// [mode]: PromptMode
136    pub fn set_prompt<M: PromptMode<U>>(mut self, prompt: Text) -> Self {
137        self.prompts.insert(TypeId::of::<M>(), prompt);
138        self
139    }
140
141    /// Places the [`PromptLine`] above, as opposed to below
142    pub fn above(self) -> Self {
143        Self {
144            specs: PushSpecs::above().ver_len(1.0),
145            ..self
146        }
147    }
148
149    /// Places the [`PromptLine`] below, this is the default
150    pub fn below(self) -> Self {
151        Self {
152            specs: PushSpecs::below().ver_len(1.0),
153            ..self
154        }
155    }
156
157    /// Hides the [`PromptLine`] by default
158    pub fn hidden(self) -> Self {
159        Self { specs: self.specs.hidden(), ..self }
160    }
161
162    /// Pushes this [`PromptLine`] to the left
163    pub fn left_ratioed(self, den: u16, div: u16) -> Self {
164        Self {
165            specs: PushSpecs::left().hor_ratio(den, div),
166            ..self
167        }
168    }
169}
170
171impl<U: Ui> Default for PromptLineCfg<U> {
172    fn default() -> Self {
173        PromptLine::cfg()
174    }
175}