1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4use std::mem;
5
6use cvars::SetGet;
7
8#[derive(Debug, Clone, Default)]
10pub struct Console {
11 pub prompt: String,
15
16 prompt_saved: Option<String>,
18
19 prompt_history_index: Option<usize>,
21
22 pub history: Vec<HistoryLine>,
26
27 pub history_view_end: usize,
32}
33
34impl Console {
35 pub fn new() -> Self {
37 Console {
38 prompt: String::new(),
39 prompt_saved: None,
40 prompt_history_index: None,
41 history: Vec::new(),
42 history_view_end: 0,
43 }
44 }
45
46 pub fn history_back(&mut self) {
51 let search_slice = if let Some(hi) = self.prompt_history_index {
52 &self.history[0..hi]
53 } else {
54 &self.history[..]
55 };
56 if let Some(new_index) = search_slice
57 .iter()
58 .rposition(|hist_line| hist_line.is_input)
59 {
60 self.prompt_history_index = Some(new_index);
61 if self.prompt_saved.is_none() {
62 self.prompt_saved = Some(self.prompt.clone());
63 }
64 self.prompt = self.history[new_index].text.clone();
65 }
66 }
67
68 pub fn history_forward(&mut self) {
72 if let Some(index) = self.prompt_history_index {
73 let begin = index + 1;
76 let search_slice = &self.history[begin..];
77 if let Some(local_index) = search_slice.iter().position(|hist_line| hist_line.is_input)
78 {
79 let new_index = begin + local_index;
83 self.prompt_history_index = Some(new_index);
84 self.prompt = self.history[new_index].text.clone();
85 } else {
86 self.prompt_history_index = None;
88 self.prompt = self.prompt_saved.take().unwrap();
89 }
90 }
91 }
92
93 pub fn history_scroll_up(&mut self, count: usize) {
95 self.history_view_end = self.history_view_end.saturating_sub(count);
96 if self.history_view_end == 0 && !self.history.is_empty() {
97 self.history_view_end = 1;
100 }
101 }
102
103 pub fn history_scroll_down(&mut self, count: usize) {
105 self.history_view_end = (self.history_view_end + count).min(self.history.len());
106 }
107
108 pub fn enter(&mut self, cvars: &mut dyn SetGet) {
110 let cmd = mem::take(&mut self.prompt);
111
112 self.print_input(&cmd);
113
114 let res = self.execute_command(cvars, &cmd);
116 if let Err(msg) = res {
117 self.print(msg);
118 }
119
120 self.prompt_history_index = None;
122 }
123
124 fn execute_command(&mut self, cvars: &mut dyn SetGet, cmd: &str) -> Result<(), String> {
126 let mut parts = cmd.split_whitespace();
128
129 let cvar_name = match parts.next() {
130 Some(name) => name,
131 None => return Ok(()),
132 };
133 if cvar_name == "help" || cvar_name == "?" {
134 self.print("Available actions:");
135 self.print(" help Print this message");
136 self.print(" <cvar name> Print the cvar's value");
137 self.print(" <cvar name> <value> Set the cvar's value");
138 return Ok(());
139 }
140
141 let cvar_value = match parts.next() {
142 Some(val) => val,
143 None => {
144 let val = cvars.get_string(cvar_name)?;
145 self.print(val);
146 return Ok(());
147 }
148 };
149 if let Some(rest) = parts.next() {
150 return Err(format!("expected only cvar name and value, found {rest}"));
151 }
152 cvars.set_str(cvar_name, cvar_value)
153 }
154
155 pub fn print<S: Into<String>>(&mut self, text: S) {
157 self.push_history_line(text.into(), false);
158 }
159
160 fn print_input<S: Into<String>>(&mut self, text: S) {
162 self.push_history_line(text.into(), true);
163 }
164
165 fn push_history_line(&mut self, text: String, is_input: bool) {
166 let hist_line = HistoryLine::new(text, is_input);
167 self.history.push(hist_line);
168
169 self.history_view_end += 1;
171 }
172}
173
174#[derive(Debug, Clone)]
178pub struct HistoryLine {
179 pub text: String,
181 pub is_input: bool,
183}
184
185impl HistoryLine {
186 pub fn new(text: String, is_input: bool) -> Self {
188 Self { text, is_input }
189 }
190}