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        handle.write(pa, |pl, area| {
68            if let Some(sels) = pl.text.selections()
69                && let Some(main) = sels.get_main()
70            {
71                area.scroll_around_point(&pl.text, main.caret(), pl.print_cfg());
72            }
73        })
74    }
75
76    fn needs_update(&self) -> bool {
77        false
78    }
79
80    fn cfg() -> Self::Cfg {
81        Self::Cfg {
82            prompts: HashMap::new(),
83            specs: PushSpecs::below().with_ver_len(1.0),
84            _ghost: PhantomData,
85        }
86    }
87
88    fn text(&self) -> &Text {
89        &self.text
90    }
91
92    fn text_mut(&mut self) -> &mut Text {
93        &mut self.text
94    }
95
96    fn print_cfg(&self) -> PrintCfg {
97        PrintCfg::default_for_input().with_forced_horizontal_scrolloff()
98    }
99
100    fn once() -> Result<(), Text> {
101        Ok(())
102    }
103}
104
105impl<U: Ui> WidgetCfg<U> for PromptLineCfg<U> {
106    type Widget = PromptLine<U>;
107
108    fn build(self, _: &mut Pass, _: Option<FileHandle<U>>) -> (Self::Widget, PushSpecs) {
109        let specs = if hook::group_exists("HidePromptLine") {
110            self.specs.with_ver_len(0.0)
111        } else {
112            self.specs
113        };
114
115        let widget = PromptLine {
116            text: Text::default(),
117            prompts: HashMap::new(),
118            _ghost: PhantomData,
119        };
120
121        (widget, specs)
122    }
123}
124
125#[doc(hidden)]
126pub struct PromptLineCfg<U> {
127    prompts: HashMap<TypeId, Text>,
128    specs: PushSpecs,
129    _ghost: PhantomData<U>,
130}
131
132impl<U: Ui> PromptLineCfg<U> {
133    /// Changes the default [prompt] for a given [mode]
134    ///
135    /// [prompt]: Text
136    /// [mode]: PromptMode
137    pub fn set_prompt<M: PromptMode<U>>(mut self, prompt: Text) -> Self {
138        self.prompts.insert(TypeId::of::<M>(), prompt);
139        self
140    }
141
142    /// Places the [`PromptLine`] above, as opposed to below
143    pub fn above(self) -> Self {
144        Self {
145            specs: PushSpecs::above().with_ver_len(1.0),
146            ..self
147        }
148    }
149
150    /// Places the [`PromptLine`] below, this is the default
151    pub fn below(self) -> Self {
152        Self {
153            specs: PushSpecs::below().with_ver_len(1.0),
154            ..self
155        }
156    }
157
158    /// Hides the [`PromptLine`] by default
159    pub fn hidden(self) -> Self {
160        Self { specs: self.specs.hidden(), ..self }
161    }
162
163    /// Pushes this [`PromptLine`] to the left
164    pub fn left_ratioed(self, den: u16, div: u16) -> Self {
165        Self {
166            specs: PushSpecs::left().with_hor_ratio(den, div),
167            ..self
168        }
169    }
170}
171
172impl<U: Ui> Default for PromptLineCfg<U> {
173    fn default() -> Self {
174        PromptLine::cfg()
175    }
176}