1const UNSUPPORTED_TERM: [&str; 3] = ["dumb", "cons25", "emacs"];
5
6use crate::config::Config;
7use crate::highlight::Highlighter;
8use crate::keys::KeyEvent;
9use crate::layout::{GraphemeClusterMode, Layout, Position, Unit};
10use crate::line_buffer::LineBuffer;
11use crate::{Cmd, Prompt, Result};
12
13pub trait RawMode: Sized {
15 fn disable_raw_mode(&self) -> Result<()>;
17}
18
19pub enum Event {
21 KeyPress(KeyEvent),
22 ExternalPrint(String),
23 #[cfg(target_os = "macos")]
24 Timeout(bool),
25}
26
27pub trait RawReader {
29 type Buffer;
30 fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event>; fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyEvent>;
34 #[cfg(unix)]
36 fn next_char(&mut self) -> Result<char>;
37 fn read_pasted_text(&mut self) -> Result<String>;
39 fn find_binding(&self, key: &KeyEvent) -> Option<Cmd>;
41 fn unbuffer(self) -> Option<Buffer>;
43}
44
45pub trait Renderer {
47 type Reader: RawReader;
48
49 fn move_cursor(&mut self, old: Position, new: Position) -> Result<()>;
50
51 fn refresh_line<P: Prompt + ?Sized>(
53 &mut self,
54 prompt: &P,
55 line: &LineBuffer,
56 hint: Option<&str>,
57 old_layout: Option<&Layout>, new_layout: &Layout,
59 highlighter: Option<&dyn Highlighter>,
60 ) -> Result<()>;
61
62 fn compute_layout(
66 &self,
67 prompt_size: Position,
68 default_prompt: bool,
69 line: &LineBuffer,
70 info: Option<&str>,
71 ) -> Layout {
72 let pos = line.pos();
74 let cursor = self.calculate_position(&line[..pos], prompt_size);
75 let mut end = if pos == line.len() {
77 cursor
78 } else {
79 self.calculate_position(&line[pos..], cursor)
80 };
81 if let Some(info) = info {
82 end = self.calculate_position(info, end);
83 }
84
85 let new_layout = Layout {
86 grapheme_cluster_mode: self.grapheme_cluster_mode(),
87 prompt_size,
88 default_prompt,
89 cursor,
90 end,
91 has_info: info.is_some(),
92 };
93 debug_assert!(new_layout.prompt_size <= new_layout.cursor);
94 debug_assert!(new_layout.cursor <= new_layout.end);
95 new_layout
96 }
97
98 fn calculate_position(&self, s: &str, orig: Position) -> Position;
101
102 fn write_and_flush(&mut self, buf: &str) -> Result<()>;
103
104 fn beep(&mut self) -> Result<()>;
107
108 fn clear_screen(&mut self) -> Result<()>;
110 fn clear_rows(&mut self, layout: &Layout) -> Result<()>;
112 fn clear_to_eol(&mut self) -> Result<()>;
114
115 fn update_size(&mut self);
117 fn get_columns(&self) -> Unit;
119 fn get_rows(&self) -> Unit;
121 fn colors_enabled(&self) -> bool;
123 fn grapheme_cluster_mode(&self) -> GraphemeClusterMode;
125
126 fn move_cursor_at_leftmost(&mut self, rdr: &mut Self::Reader) -> Result<()>;
128 fn begin_synchronized_update(&mut self) -> Result<()> {
130 Ok(())
131 }
132 fn end_synchronized_update(&mut self) -> Result<()> {
134 Ok(())
135 }
136}
137
138fn width(gcm: GraphemeClusterMode, s: &str, esc_seq: &mut u8) -> Unit {
140 if *esc_seq == 1 {
141 if s == "[" {
142 *esc_seq = 2;
144 } else {
145 *esc_seq = 0;
147 }
148 0
149 } else if *esc_seq == 2 {
150 if s == ";" || (s.as_bytes()[0] >= b'0' && s.as_bytes()[0] <= b'9') {
151 } else {
155 *esc_seq = 0;
157 }
158 0
159 } else if s == "\x1b" {
160 *esc_seq = 1;
161 0
162 } else if s == "\n" {
163 0
164 } else {
165 gcm.width(s)
166 }
167}
168
169pub trait ExternalPrinter {
171 fn print(&mut self, msg: String) -> Result<()>;
173}
174
175pub trait Term {
177 type Buffer;
178 type KeyMap;
179 type Reader: RawReader<Buffer = Self::Buffer>; type Writer: Renderer<Reader = Self::Reader>; type Mode: RawMode;
182 type ExternalPrinter: ExternalPrinter;
183 type CursorGuard;
184
185 fn new(config: &Config) -> Result<Self>
186 where
187 Self: Sized;
188 fn is_unsupported(&self) -> bool;
191 fn is_input_tty(&self) -> bool;
193 fn is_output_tty(&self) -> bool;
195 fn enable_raw_mode(&mut self, config: &Config) -> Result<(Self::Mode, Self::KeyMap)>;
197 fn create_reader(
199 &self,
200 buffer: Option<Self::Buffer>,
201 config: &Config,
202 key_map: Self::KeyMap,
203 ) -> Result<Self::Reader>;
204 fn create_writer(&self, config: &Config) -> Self::Writer;
206 fn writeln(&self) -> Result<()>;
207 fn create_external_printer(&mut self) -> Result<Self::ExternalPrinter>;
209 fn set_cursor_visibility(&mut self, visible: bool) -> Result<Option<Self::CursorGuard>>;
211}
212
213fn is_unsupported_term() -> bool {
216 match std::env::var("TERM") {
217 Ok(term) => {
218 for iter in &UNSUPPORTED_TERM {
219 if (*iter).eq_ignore_ascii_case(&term) {
220 return true;
221 }
222 }
223 false
224 }
225 Err(_) => false,
226 }
227}
228
229#[cfg(all(windows, not(target_arch = "wasm32")))]
232mod windows;
233#[cfg(all(windows, not(target_arch = "wasm32"), not(test)))]
234pub use self::windows::*;
235
236#[cfg(all(unix, not(target_arch = "wasm32")))]
239mod unix;
240#[cfg(all(unix, not(target_arch = "wasm32"), not(test)))]
241pub use self::unix::*;
242
243#[cfg(any(test, target_arch = "wasm32"))]
244mod test;
245#[cfg(any(test, target_arch = "wasm32"))]
246pub use self::test::*;
247
248#[cfg(test)]
249mod test_ {
250 #[test]
251 fn test_unsupported_term() {
252 std::env::set_var("TERM", "xterm");
253 assert!(!super::is_unsupported_term());
254
255 std::env::set_var("TERM", "dumb");
256 assert!(super::is_unsupported_term());
257 }
258}