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}