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, 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(
53        &mut self,
54        prompt: &str,
55        line: &LineBuffer,
56        hint: Option<&str>,
57        old_layout: &Layout,
58        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        };
92        debug_assert!(new_layout.prompt_size <= new_layout.cursor);
93        debug_assert!(new_layout.cursor <= new_layout.end);
94        new_layout
95    }
96
97    fn calculate_position(&self, s: &str, orig: Position) -> Position;
100
101    fn write_and_flush(&mut self, buf: &str) -> Result<()>;
102
103    fn beep(&mut self) -> Result<()>;
106
107    fn clear_screen(&mut self) -> Result<()>;
109    fn clear_rows(&mut self, layout: &Layout) -> Result<()>;
111
112    fn update_size(&mut self);
114    fn get_columns(&self) -> Unit;
116    fn get_rows(&self) -> Unit;
118    fn colors_enabled(&self) -> bool;
120    fn grapheme_cluster_mode(&self) -> GraphemeClusterMode;
122
123    fn move_cursor_at_leftmost(&mut self, rdr: &mut Self::Reader) -> Result<()>;
125    fn begin_synchronized_update(&mut self) -> Result<()> {
127        Ok(())
128    }
129    fn end_synchronized_update(&mut self) -> Result<()> {
131        Ok(())
132    }
133}
134
135fn width(gcm: GraphemeClusterMode, s: &str, esc_seq: &mut u8) -> Unit {
137    if *esc_seq == 1 {
138        if s == "[" {
139            *esc_seq = 2;
141        } else {
142            *esc_seq = 0;
144        }
145        0
146    } else if *esc_seq == 2 {
147        if s == ";" || (s.as_bytes()[0] >= b'0' && s.as_bytes()[0] <= b'9') {
148            } else {
152            *esc_seq = 0;
154        }
155        0
156    } else if s == "\x1b" {
157        *esc_seq = 1;
158        0
159    } else if s == "\n" {
160        0
161    } else {
162        gcm.width(s)
163    }
164}
165
166pub trait ExternalPrinter {
168    fn print(&mut self, msg: String) -> Result<()>;
170}
171
172pub trait Term {
174    type Buffer;
175    type KeyMap;
176    type Reader: RawReader<Buffer = Self::Buffer>; type Writer: Renderer<Reader = Self::Reader>; type Mode: RawMode;
179    type ExternalPrinter: ExternalPrinter;
180    type CursorGuard;
181
182    fn new(config: Config) -> Result<Self>
183    where
184        Self: Sized;
185    fn is_unsupported(&self) -> bool;
188    fn is_input_tty(&self) -> bool;
190    fn is_output_tty(&self) -> bool;
192    fn enable_raw_mode(&mut self) -> Result<(Self::Mode, Self::KeyMap)>;
194    fn create_reader(
196        &self,
197        buffer: Option<Self::Buffer>,
198        config: &Config,
199        key_map: Self::KeyMap,
200    ) -> Self::Reader;
201    fn create_writer(&self) -> Self::Writer;
203    fn writeln(&self) -> Result<()>;
204    fn create_external_printer(&mut self) -> Result<Self::ExternalPrinter>;
206    fn set_cursor_visibility(&mut self, visible: bool) -> Result<Option<Self::CursorGuard>>;
208}
209
210fn is_unsupported_term() -> bool {
213    match std::env::var("TERM") {
214        Ok(term) => {
215            for iter in &UNSUPPORTED_TERM {
216                if (*iter).eq_ignore_ascii_case(&term) {
217                    return true;
218                }
219            }
220            false
221        }
222        Err(_) => false,
223    }
224}
225
226#[cfg(all(windows, not(target_arch = "wasm32")))]
229mod windows;
230#[cfg(all(windows, not(target_arch = "wasm32"), not(test)))]
231pub use self::windows::*;
232
233#[cfg(all(unix, not(target_arch = "wasm32")))]
236mod unix;
237#[cfg(all(unix, not(target_arch = "wasm32"), not(test)))]
238pub use self::unix::*;
239
240#[cfg(any(test, target_arch = "wasm32"))]
241mod test;
242#[cfg(any(test, target_arch = "wasm32"))]
243pub use self::test::*;
244
245#[cfg(test)]
246mod test_ {
247    #[test]
248    fn test_unsupported_term() {
249        std::env::set_var("TERM", "xterm");
250        assert!(!super::is_unsupported_term());
251
252        std::env::set_var("TERM", "dumb");
253        assert!(super::is_unsupported_term());
254    }
255}