1use std::cell::RefCell;
16use std::collections::HashMap;
17use std::collections::VecDeque;
18use std::rc::Rc;
19
20use cursive::event::Event;
21use cursive::event::EventResult;
22use cursive::event::Key;
23use cursive::theme::ColorStyle;
24use cursive::vec::Vec2;
25use cursive::views::EditView;
26use cursive::views::NamedView;
27use cursive::Cursive;
28use cursive::Printer;
29use cursive::View;
30
31use crate::controllers::Controllers;
32use crate::stats_view::StatsView;
33use crate::stats_view::ViewBridge;
34
35const MAX_CMD_HISTORY: usize = 10;
36
37#[derive(PartialEq)]
43enum CPMode {
44 Info,
45 Alert,
46 Command,
47}
48
49pub struct CommandPalette {
55 content: String,
56 filter_info: Option<(String, String)>,
57 fold: bool,
58 mode: CPMode,
59 cmd_view: RefCell<EditView>,
60 cmd_controllers: Rc<RefCell<HashMap<&'static str, Controllers>>>,
61 cmd_history: VecDeque<String>,
62 cur_cmd_idx: usize,
63}
64
65impl View for CommandPalette {
66 fn draw(&self, printer: &Printer) {
67 let mut max_x = printer.size.x;
69
70 printer.print_hline((0, 0), printer.size.x, "─");
71 if let Some((field, filter)) = &self.filter_info {
72 let output = format!(
73 "|| Filtered Column: {:>10.10} | Filter: {:>10.10} ||",
74 field, filter
75 );
76 max_x -= output.len();
77 printer.print((max_x, 0), &output);
78 }
79
80 if self.fold {
81 let text = "| Fold |";
82 max_x -= text.len();
83 printer.print((max_x, 0), text);
84 }
85
86 match self.mode {
87 CPMode::Command => {
88 printer.print((0, 1), ":");
89 let inner_printer = printer.offset((1, 1));
90 self.cmd_view.borrow_mut().layout(inner_printer.size);
91 self.cmd_view.borrow().draw(&inner_printer);
92 }
93 _ => {
94 let mut msg_len_left = self.content.len();
96 let mut idx = 0;
97 let mut line = 1;
98 while msg_len_left > printer.size.x {
99 self.print(printer, (0, line), idx);
100 msg_len_left -= printer.size.x;
101 idx += printer.size.x;
102 line += 1;
103 }
104 self.print(printer, (0, line), idx);
105 }
106 }
107 }
108
109 fn on_event(&mut self, event: Event) -> EventResult {
110 match event {
111 Event::Key(Key::Up) => {
112 self.prev_cmd();
113 EventResult::Consumed(None)
114 }
115 Event::Key(Key::Down) => {
116 self.next_cmd();
117 EventResult::Consumed(None)
118 }
119 _ => self.cmd_view.borrow_mut().on_event(event),
120 }
121 }
122
123 fn required_size(&mut self, constraint: Vec2) -> Vec2 {
124 Vec2::new(1, self.content.len() / constraint.x + 2)
125 }
126}
127
128impl CommandPalette {
129 pub fn new<V: 'static + ViewBridge>(
131 name: &'static str,
132 content: &str,
133 cmd_controllers: Rc<RefCell<HashMap<&'static str, Controllers>>>,
134 ) -> Self {
135 Self {
136 content: content.into(),
137 filter_info: None,
138 fold: false,
139 mode: CPMode::Info,
140 cmd_view: RefCell::new(
141 EditView::new()
142 .on_submit(move |c, cmd| {
143 Self::handle_cmd_history(name, c, cmd);
144 Self::run_cmd::<V>(name, c, cmd)
145 })
146 .style(ColorStyle::terminal_default()),
147 ),
148 cmd_controllers,
149 cmd_history: VecDeque::new(),
150 cur_cmd_idx: 0,
151 }
152 }
153
154 fn handle_cmd_history(name: &'static str, c: &mut Cursive, cmd: &str) {
155 c.call_on_name(
156 &format!("{}_cmd_palette", name),
157 |cp: &mut NamedView<CommandPalette>| {
158 let mut cmd_palette = cp.get_mut();
159 cmd_palette.cmd_history.push_back(cmd.into());
160 if cmd_palette.cmd_history.len() > MAX_CMD_HISTORY {
161 cmd_palette.cmd_history.pop_front();
162 }
163 cmd_palette.cur_cmd_idx = cmd_palette.cmd_history.len() - 1;
164 },
165 );
166 }
167
168 fn prev_cmd(&mut self) {
169 if self.cmd_history.is_empty() {
170 return;
171 }
172 self.cmd_view
173 .borrow_mut()
174 .set_content(&self.cmd_history[self.cur_cmd_idx]);
175 if self.cur_cmd_idx > 0 {
176 self.cur_cmd_idx -= 1;
177 }
178 }
179
180 fn next_cmd(&mut self) {
181 if self.cmd_history.is_empty() {
182 return;
183 }
184 if self.cur_cmd_idx == self.cmd_history.len() - 1 {
185 self.cmd_view.borrow_mut().set_content("");
186 } else {
187 self.cur_cmd_idx += 1;
188 self.cmd_view
189 .borrow_mut()
190 .set_content(&self.cmd_history[self.cur_cmd_idx]);
191 }
192 }
193
194 pub fn run_cmd<V: 'static + ViewBridge>(name: &'static str, c: &mut Cursive, cmd: &str) {
198 let cmd_vec = cmd.trim().split(' ').collect::<Vec<&str>>();
199 let controller = c
200 .find_name::<Self>(&format!("{}_cmd_palette", name))
201 .expect("Fail to get cmd_palette")
202 .cmd_controllers
203 .borrow()
204 .get(cmd_vec[0])
205 .unwrap_or(&Controllers::Unknown)
206 .clone();
207
208 match controller {
209 Controllers::Unknown => {
210 let mut cp = c
211 .find_name::<Self>(&format!("{}_cmd_palette", name))
212 .expect("Fail to get cmd_palette");
213 cp.mode = CPMode::Alert;
214 cp.content = "Unknown Command".into();
215 cp.cmd_view.borrow_mut().set_content("");
216 }
217 _ => {
218 controller.handle(&mut StatsView::<V>::get_view(c), &cmd_vec);
219 controller.callback::<V>(c, &cmd_vec);
220 c.call_on_name(
221 &format!("{}_cmd_palette", name),
222 |cp: &mut NamedView<CommandPalette>| {
223 cp.get_mut().reset_cmd();
224 },
225 );
226 }
227 }
228 }
229
230 pub fn reset_cmd(&mut self) {
231 self.mode = CPMode::Info;
232 self.cmd_view.borrow_mut().set_content("");
233 }
234
235 pub fn invoke_cmd(&mut self) {
237 self.mode = CPMode::Command;
238 self.content = "".into()
239 }
240
241 pub fn is_cmd_mode(&self) -> bool {
243 self.mode == CPMode::Command
244 }
245
246 pub fn set_info<T: Into<String>>(&mut self, content: T) {
248 self.content = content.into();
249 if self.mode != CPMode::Command {
250 self.mode = CPMode::Info;
251 }
252 }
253
254 pub fn set_alert<T: Into<String>>(&mut self, content: T) {
257 if self.mode == CPMode::Alert {
258 self.content = format!("{} |=| {}", self.content, content.into());
260 } else {
261 self.content = content.into();
262 if self.mode != CPMode::Command {
263 self.mode = CPMode::Alert;
264 }
265 }
266 }
267
268 pub fn set_filter(&mut self, filter_info: Option<(String, String)>) {
269 self.filter_info = filter_info;
270 }
271
272 pub fn toggle_fold(&mut self) {
273 self.fold = !self.fold;
274 }
275
276 fn print_info(&self, printer: &Printer, pos: Vec2, idx: usize) {
277 if idx + printer.size.x > self.content.len() {
278 printer.print(pos, &self.content[idx..]);
279 } else {
280 printer.print(pos, &self.content[idx..idx + printer.size.x]);
281 }
282 }
283
284 fn print_alert(&self, printer: &Printer, pos: Vec2, idx: usize) {
285 printer.with_color(ColorStyle::title_primary(), |printer| {
286 if idx + printer.size.x > self.content.len() {
287 printer.print(pos, &self.content[idx..]);
288 } else {
289 printer.print(pos, &self.content[idx..idx + printer.size.x]);
290 }
291 })
292 }
293
294 fn print<T: Into<Vec2>>(&self, printer: &Printer, pos: T, idx: usize) {
295 match self.mode {
296 CPMode::Info => self.print_info(printer, pos.into(), idx),
297 CPMode::Alert => self.print_alert(printer, pos.into(), idx),
298 _ => {}
299 }
300 }
301
302 pub fn is_alerting(&self) -> bool {
303 self.mode == CPMode::Alert
304 }
305
306 pub fn get_content(&self) -> &str {
307 &self.content
308 }
309}