diff --git a/Cargo.toml b/Cargo.toml index f4b8df3..3ab0ac4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,3 @@ authors = ["Ticki "] [target.'cfg(not(target_os = "redox"))'.dependencies] libc = "0.2.8" - -[features] -default = ["nightly"] -nightly = [] diff --git a/README.md b/README.md index d6c2b33..3ea7617 100644 --- a/README.md +++ b/README.md @@ -19,21 +19,11 @@ and this crate can generally be considered stable. ## Cargo.toml -For nightly, add - ```toml [dependencies.termion] git = "https://github.com/ticki/termion.git" ``` -For stable, - -```toml -[dependencies.termion] -git = "https://github.com/ticki/termion.git" -default-features = false -``` - ## Features - Raw mode. @@ -53,6 +43,7 @@ default-features = false - Special keys events (modifiers, special keys, etc.). - Allocation-free. - Asynchronous key events. +- Mouse input - Carefully tested. and much more. @@ -86,10 +77,6 @@ For a more complete example, see [a minesweeper implementation](https://github.c -## TODO - -- Mouse input - ## License MIT/X11. diff --git a/examples/keys.rs b/examples/keys.rs index 03ee9f7..cda9641 100644 --- a/examples/keys.rs +++ b/examples/keys.rs @@ -18,15 +18,14 @@ fn main() { match c.unwrap() { Key::Char('q') => break, - Key::Char(c) => writeln!(stdout, "{}", c).unwrap(), - Key::Alt(c) => writeln!(stdout, "^{}", c).unwrap(), - Key::Ctrl(c) => writeln!(stdout, "*{}", c).unwrap(), - Key::Left => writeln!(stdout, "←").unwrap(), - Key::Right => writeln!(stdout, "→").unwrap(), - Key::Up => writeln!(stdout, "↑").unwrap(), - Key::Down => writeln!(stdout, "↓").unwrap(), - Key::Backspace => writeln!(stdout, "×").unwrap(), - Key::Invalid => writeln!(stdout, "???").unwrap(), + Key::Char(c) => println!("{}", c), + Key::Alt(c) => println!("^{}", c), + Key::Ctrl(c) => println!("*{}", c), + Key::Left => println!("←"), + Key::Right => println!("→"), + Key::Up => println!("↑"), + Key::Down => println!("↓"), + Key::Backspace => println!("×"), _ => {}, } stdout.flush().unwrap(); diff --git a/examples/mouse.rs b/examples/mouse.rs new file mode 100644 index 0000000..9d8a598 --- /dev/null +++ b/examples/mouse.rs @@ -0,0 +1,41 @@ +extern crate termion; + +fn main() { + use termion::{TermRead, TermWrite, IntoRawMode, Key, Event, MouseEvent}; + use std::io::{Write, stdout, stdin}; + + let stdin = stdin(); + let mut stdout = stdout().into_raw_mode().unwrap().with_mouse().unwrap(); + + stdout.clear().unwrap(); + stdout.goto(0, 0).unwrap(); + stdout.write(b"q to exit. Type stuff, use alt, click around...").unwrap(); + stdout.flush().unwrap(); + + let mut x = 0; + let mut y = 0; + + for c in stdin.events() { + stdout.goto(5, 5).unwrap(); + stdout.clear_line().unwrap(); + let evt = c.unwrap(); + match evt { + Event::Key(Key::Char('q')) => break, + Event::Mouse(me) => { + match me { + MouseEvent::Press(_, a, b) | + MouseEvent::Release(a, b) => { + x = a; + y = b; + } + } + } + _ => {} + } + println!("{:?}", evt); + stdout.goto(x, y).unwrap(); + stdout.flush().unwrap(); + } + + stdout.show_cursor().unwrap(); +} diff --git a/src/cursor.rs b/src/cursor.rs index a169c2d..093016f 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -46,3 +46,23 @@ impl fmt::Display for Right { write!(f, csi!("{}C"), self.0) } } + +/// Move cursor up. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Up(pub u16); + +impl fmt::Display for Up { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, csi!("{}A"), self.0) + } +} + +/// Move cursor down. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Down(pub u16); + +impl fmt::Display for Down { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, csi!("{}B"), self.0) + } +} diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 0000000..fde57f4 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,285 @@ +use std::io::{Error, ErrorKind}; +use std::ascii::AsciiExt; +use std::str; + +/// An event reported by the terminal. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Event { + /// A key press. + Key(Key), + /// A mouse button press, release or wheel use at specific coordinates. + Mouse(MouseEvent), + /// An event that cannot currently be evaluated. + Unsupported, +} + +/// A mouse related event. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum MouseEvent { + /// A mouse button was pressed. + Press(MouseButton, u16, u16), + /// A mouse button was released. + Release(u16, u16), +} + +/// A mouse button. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum MouseButton { + /// The left mouse button. + Left, + /// The right mouse button. + Right, + /// The middle mouse button. + Middle, + /// Mouse wheel is going up. + /// + /// This event is typically only used with Mouse::Press. + WheelUp, + /// Mouse wheel is going down. + /// + /// This event is typically only used with Mouse::Press. + WheelDown, +} + +/// A key. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Key { + /// Backspace. + Backspace, + /// Left arrow. + Left, + /// Right arrow. + Right, + /// Up arrow. + Up, + /// Down arrow. + Down, + /// Home key. + Home, + /// End key. + End, + /// Page Up key. + PageUp, + /// Page Down key. + PageDown, + /// Delete key. + Delete, + /// Insert key. + Insert, + /// Function keys. + /// + /// Only function keys 1 through 12 are supported. + F(u8), + /// Normal character. + Char(char), + /// Alt modified character. + Alt(char), + /// Ctrl modified character. + /// + /// Note that certain keys may not be modifiable with `ctrl`, due to limitations of terminals. + Ctrl(char), + /// Null byte. + Null, + + #[allow(missing_docs)] + #[doc(hidden)] + __IsNotComplete +} + +/// Parse an Event from `item` and possibly subsequent bytes through `iter`. +pub fn parse_event(item: Result, iter: &mut I) -> Result +where I: Iterator> +{ + let error = Err(Error::new(ErrorKind::Other, "Could not parse an event")); + match item { + Ok(b'\x1B') => { + Ok(match iter.next() { + Some(Ok(b'O')) => { + match iter.next() { + Some(Ok(val @ b'P' ... b'S')) => Event::Key(Key::F(1 + val - b'P')), + _ => return error, + } + } + Some(Ok(b'[')) => { + match iter.next() { + Some(Ok(b'D')) => Event::Key(Key::Left), + Some(Ok(b'C')) => Event::Key(Key::Right), + Some(Ok(b'A')) => Event::Key(Key::Up), + Some(Ok(b'B')) => Event::Key(Key::Down), + Some(Ok(b'H')) => Event::Key(Key::Home), + Some(Ok(b'F')) => Event::Key(Key::End), + Some(Ok(b'M')) => { + // X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only). + let cb = iter.next().unwrap().unwrap() as i8 - 32; + // (1, 1) are the coords for upper left. + let cx = (iter.next().unwrap().unwrap() as u8 - 1).saturating_sub(32) as u16; + let cy = (iter.next().unwrap().unwrap() as u8 - 1).saturating_sub(32) as u16; + Event::Mouse(match cb & 0b11 { + 0 => { + if cb & 0x40 != 0 { + MouseEvent::Press(MouseButton::WheelUp, cx, cy) + } else { + MouseEvent::Press(MouseButton::Left, cx, cy) + } + } + 1 => { + if cb & 0x40 != 0 { + MouseEvent::Press(MouseButton::WheelDown, cx, cy) + } else { + MouseEvent::Press(MouseButton::Middle, cx, cy) + } + } + 2 => MouseEvent::Press(MouseButton::Right, cx, cy), + 3 => MouseEvent::Release(cx, cy), + _ => return error, + }) + } + Some(Ok(b'<')) => { + // xterm mouse encoding: + // ESC [ < Cb ; Cx ; Cy ; (M or m) + let mut buf = Vec::new(); + let mut c = iter.next().unwrap().unwrap(); + while match c { + b'm' | b'M' => false, + _ => true, + } { + buf.push(c); + c = iter.next().unwrap().unwrap(); + } + let str_buf = String::from_utf8(buf).unwrap(); + let ref mut nums = str_buf.split(';'); + + let cb = nums.next().unwrap().parse::().unwrap(); + let cx = nums.next().unwrap().parse::().unwrap() - 1; + let cy = nums.next().unwrap().parse::().unwrap() - 1; + + let button = match cb { + 0 => MouseButton::Left, + 1 => MouseButton::Middle, + 2 => MouseButton::Right, + 64 => MouseButton::WheelUp, + 65 => MouseButton::WheelDown, + _ => return error, + }; + Event::Mouse(match c { + b'M' => MouseEvent::Press(button, cx, cy), + b'm' => MouseEvent::Release(cx, cy), + _ => return error, + + }) + } + Some(Ok(c @ b'0'...b'9')) => { + // Numbered escape code. + let mut buf = Vec::new(); + buf.push(c); + let mut c = iter.next().unwrap().unwrap(); + while match c { + b'M' | b'~' => false, + _ => true, + } { + buf.push(c); + c = iter.next().unwrap().unwrap(); + } + + match c { + // rxvt mouse encoding: + // ESC [ Cb ; Cx ; Cy ; M + b'M' => { + let str_buf = String::from_utf8(buf).unwrap(); + let ref mut nums = str_buf.split(';'); + + let cb = nums.next().unwrap().parse::().unwrap(); + let cx = nums.next().unwrap().parse::().unwrap() - 1; + let cy = nums.next().unwrap().parse::().unwrap() - 1; + + let event = match cb { + 32 => MouseEvent::Press(MouseButton::Left, cx, cy), + 33 => MouseEvent::Press(MouseButton::Middle, cx, cy), + 34 => MouseEvent::Press(MouseButton::Right, cx, cy), + 35 => MouseEvent::Release(cx, cy), + 96 => MouseEvent::Press(MouseButton::WheelUp, cx, cy), + 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy), + _ => return error, + }; + + Event::Mouse(event) + }, + // Special key code. + b'~' => { + let num: u8 = String::from_utf8(buf).unwrap().parse().unwrap(); + match num { + 1 | 7 => Event::Key(Key::Home), + 2 => Event::Key(Key::Insert), + 3 => Event::Key(Key::Delete), + 4 | 8 => Event::Key(Key::End), + 5 => Event::Key(Key::PageUp), + 6 => Event::Key(Key::PageDown), + v @ 11...15 => Event::Key(Key::F(v - 10)), + v @ 17...21 => Event::Key(Key::F(v - 11)), + v @ 23...24 => Event::Key(Key::F(v - 12)), + _ => return error, + } + } + _ => return error, + } + } + _ => return error, + } + } + Some(Ok(c)) => { + let ch = parse_utf8_char(c, iter); + Event::Key(Key::Alt(try!(ch))) + } + Some(Err(_)) | None => return error, + }) + } + Ok(b'\n') | Ok(b'\r') => Ok(Event::Key(Key::Char('\n'))), + Ok(b'\t') => Ok(Event::Key(Key::Char('\t'))), + Ok(b'\x7F') => Ok(Event::Key(Key::Backspace)), + Ok(c @ b'\x01'...b'\x1A') => Ok(Event::Key(Key::Ctrl((c as u8 - 0x1 + b'a') as char))), + Ok(c @ b'\x1C'...b'\x1F') => { + Ok(Event::Key(Key::Ctrl((c as u8 - 0x1C + b'4') as char))) + } + Ok(b'\0') => Ok(Event::Key(Key::Null)), + Ok(c) => { + Ok({ + let ch = parse_utf8_char(c, iter); + Event::Key(Key::Char(try!(ch))) + }) + } + Err(e) => Err(e), + } +} + +/// Parse `c` as either a single byte ASCII char or a variable size UTF-8 char. +fn parse_utf8_char(c: u8, iter: &mut I) -> Result +where I: Iterator> +{ + let error = Err(Error::new(ErrorKind::Other, "Input character is not valid UTF-8")); + if c.is_ascii() { + Ok(c as char) + } else { + let ref mut bytes = Vec::new(); + bytes.push(c); + + loop { + bytes.push(iter.next().unwrap().unwrap()); + match str::from_utf8(bytes) { + Ok(st) => return Ok(st.chars().next().unwrap()), + Err(_) => {}, + } + if bytes.len() >= 4 { return error; } + } + } +} + +#[test] +fn test_parse_utf8() { + let st = "abcéŷ¤£€ù%323"; + let ref mut bytes = st.bytes().map(|x| Ok(x)); + let chars = st.chars(); + for c in chars { + let b = bytes.next().unwrap().unwrap(); + assert!(c == parse_utf8_char(b, bytes).unwrap()); + } +} diff --git a/src/input.rs b/src/input.rs index 55a7ac0..9361d28 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,81 +1,55 @@ //! Input. use std::io::{self, Read, Write}; +use event::{parse_event, Event, Key}; use raw::IntoRawMode; -/// A key. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum Key { - /// Backspace. - Backspace, - /// Left arrow. - Left, - /// Right arrow. - Right, - /// Up arrow. - Up, - /// Down arrow. - Down, - /// Normal character. - Char(char), - /// Alt modified character. - Alt(char), - /// Ctrl modified character. - /// - /// Note that certain keys may not be modifiable with `ctrl`, due to limitations of terminals. - Ctrl(char), - /// Invalid character code. - Invalid, - /// Null byte. - Null, - - #[allow(missing_docs)] - #[doc(hidden)] - __IsNotComplete -} /// An iterator over input keys. -#[cfg(feature = "nightly")] pub struct Keys { - chars: I, + iter: Events, } -#[cfg(feature = "nightly")] -impl>> Iterator for Keys { - type Item = Result; +impl>> Iterator for Keys { + type Item = Result; - fn next(&mut self) -> Option> { - Some(match self.chars.next() { - Some(Ok('\x1B')) => Ok(match self.chars.next() { - Some(Ok('[')) => match self.chars.next() { - Some(Ok('D')) => Key::Left, - Some(Ok('C')) => Key::Right, - Some(Ok('A')) => Key::Up, - Some(Ok('B')) => Key::Down, - _ => Key::Invalid, - }, - Some(Ok(c)) => Key::Alt(c), - Some(Err(_)) | None => Key::Invalid, - }), - Some(Ok('\n')) | Some(Ok('\r')) => Ok(Key::Char('\n')), - Some(Ok('\t')) => Ok(Key::Char('\t')), - Some(Ok('\x7F')) => Ok(Key::Backspace), - Some(Ok(c @ '\x01' ... '\x1A')) => Ok(Key::Ctrl((c as u8 - 0x1 + b'a') as char)), - Some(Ok(c @ '\x1C' ... '\x1F')) => Ok(Key::Ctrl((c as u8 - 0x1C + b'4') as char)), - Some(Ok('\0')) => Ok(Key::Null), - Some(Ok(c)) => Ok(Key::Char(c)), - Some(Err(e)) => Err(e), - None => return None, - }) + fn next(&mut self) -> Option> { + loop { + match self.iter.next() { + Some(Ok(Event::Key(k))) => return Some(Ok(k)), + Some(Ok(_)) => continue, + e @ Some(Err(_)) => e, + None => return None, + }; + } + } +} + +/// An iterator over input events. +pub struct Events { + bytes: I, +} + +impl>> Iterator for Events { + type Item = Result; + + fn next(&mut self) -> Option> { + let ref mut iter = self.bytes; + match iter.next() { + Some(item) => Some(parse_event(item, iter).or(Ok(Event::Unsupported))), + None => None, + } } } /// Extension to `Read` trait. pub trait TermRead { + /// An iterator over input events. + fn events(self) -> Events> where Self: Sized; + /// An iterator over key inputs. - #[cfg(feature = "nightly")] - fn keys(self) -> Keys> where Self: Sized; + fn keys(self) -> Keys> where Self: Sized; /// Read a line. /// @@ -95,10 +69,14 @@ pub trait TermRead { impl TermRead for R { - #[cfg(feature = "nightly")] - fn keys(self) -> Keys> { + fn events(self) -> Events> { + Events { + bytes: self.bytes(), + } + } + fn keys(self) -> Keys> { Keys { - chars: self.chars(), + iter: self.events(), } } @@ -124,8 +102,8 @@ impl TermRead for R { mod test { use super::*; use std::io; + use event::{Key, Event, MouseEvent, MouseButton}; - #[cfg(feature = "nightly")] #[test] fn test_keys() { let mut i = b"\x1Bayo\x7F\x1B[D".keys(); @@ -138,6 +116,53 @@ mod test { assert!(i.next().is_none()); } + #[test] + fn test_events() { + let mut i = b"\x1B[\x00bc\x7F\x1B[D\ + \x1B[M\x00\x22\x24\x1B[<0;2;4;M\x1B[32;2;4M\x1B[<0;2;4;m\x1B[35;2;4Mb".events(); + + assert_eq!(i.next().unwrap().unwrap(), Event::Unsupported); + assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b'))); + assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('c'))); + assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Backspace)); + assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Left)); + assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::WheelUp, 1, 3))); + assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::Left, 1, 3))); + assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Press(MouseButton::Left, 1, 3))); + assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(1, 3))); + assert_eq!(i.next().unwrap().unwrap(), Event::Mouse(MouseEvent::Release(1, 3))); + assert_eq!(i.next().unwrap().unwrap(), Event::Key(Key::Char('b'))); + assert!(i.next().is_none()); + } + + #[test] + fn test_function_keys() { + let mut st = b"\x1BOP\x1BOQ\x1BOR\x1BOS".keys(); + for i in 1 .. 5 { + assert_eq!(st.next().unwrap().unwrap(), Key::F(i)); + } + + let mut st = b"\x1B[11~\x1B[12~\x1B[13~\x1B[14~\x1B[15~\ + \x1B[17~\x1B[18~\x1B[19~\x1B[20~\x1B[21~\x1B[23~\x1B[24~".keys(); + for i in 1 .. 13 { + assert_eq!(st.next().unwrap().unwrap(), Key::F(i)); + } + } + + #[test] + fn test_special_keys() { + let mut st = b"\x1B[2~\x1B[H\x1B[7~\x1B[5~\x1B[3~\x1B[F\x1B[8~\x1B[6~".keys(); + assert_eq!(st.next().unwrap().unwrap(), Key::Insert); + assert_eq!(st.next().unwrap().unwrap(), Key::Home); + assert_eq!(st.next().unwrap().unwrap(), Key::Home); + assert_eq!(st.next().unwrap().unwrap(), Key::PageUp); + assert_eq!(st.next().unwrap().unwrap(), Key::Delete); + assert_eq!(st.next().unwrap().unwrap(), Key::End); + assert_eq!(st.next().unwrap().unwrap(), Key::End); + assert_eq!(st.next().unwrap().unwrap(), Key::PageDown); + assert!(st.next().is_none()); + } + fn line_match(a: &str, b: Option<&str>) { let mut sink = io::sink(); @@ -181,4 +206,5 @@ mod test { line_match("abc\x03https://www.youtube.com/watch?v=dQw4w9WgXcQ", None); line_match("hello\x04https://www.youtube.com/watch?v=yPYZpwSpKmA", None); } + } diff --git a/src/lib.rs b/src/lib.rs index 285d15f..2c6ff43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,9 +11,6 @@ //! For more information refer to the [README](https://github.com/ticki/termion). #![warn(missing_docs)] -#![cfg_attr(feature = "nightly", feature(io))] - - #[cfg(not(target_os = "redox"))] extern crate libc; @@ -34,6 +31,8 @@ mod macros; pub mod clear; pub mod color; pub mod cursor; +pub mod event; pub mod input; pub mod raw; +pub mod scroll; pub mod style; diff --git a/src/raw.rs b/src/raw.rs index 1c097ff..e1d21e3 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -28,6 +28,17 @@ pub struct RawTerminal { output: W, } +#[cfg(not(target_os = "redox"))] +impl RawTerminal + where W: Write +{ + /// Enable mouse support. + pub fn with_mouse(mut self) -> io::Result> { + try!(self.write(ENTER_MOUSE_SEQUENCE)); + Ok(MouseTerminal { term: self }) + } +} + #[cfg(not(target_os = "redox"))] impl Drop for RawTerminal { fn drop(&mut self) { @@ -64,9 +75,9 @@ impl Write for RawTerminal { pub trait IntoRawMode: Write + Sized { /// Switch to raw mode. /// - /// Raw mode means that stdin won't be printed (it will instead have to be written manually by the - /// program). Furthermore, the input isn't canonicalised or buffered (that is, you can read from - /// stdin one byte of a time). The output is neither modified in any way. + /// Raw mode means that stdin won't be printed (it will instead have to be written manually by + /// the program). Furthermore, the input isn't canonicalised or buffered (that is, you can + /// read from stdin one byte of a time). The output is neither modified in any way. fn into_raw_mode(self) -> io::Result>; } @@ -88,10 +99,11 @@ impl IntoRawMode for W { if set_terminal_attr(&mut ios as *mut _) != 0 { Err(io::Error::new(io::ErrorKind::Other, "Unable to set Termios attribute.")) } else { - Ok(RawTerminal { + let res = RawTerminal { prev_ios: prev_ios, output: self, - }) + }; + Ok(res) } } @@ -99,12 +111,63 @@ impl IntoRawMode for W { fn into_raw_mode(mut self) -> io::Result> { use control::TermWrite; - self.csi(b"r").map(|_| RawTerminal { - output: self, + self.csi(b"r").map(|_| { + let mut res = RawTerminal { output: self }; + res }) } } +/// A sequence of escape codes to enable terminal mouse support. +const ENTER_MOUSE_SEQUENCE: &'static [u8] = b"\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h"; + +/// A sequence of escape codes to disable terminal mouse support. +const EXIT_MOUSE_SEQUENCE: &'static [u8] = b"\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l"; + +/// A `RawTerminal` with added mouse support. +/// +/// To get such a terminal handle use `RawTerminal`'s +/// [`with_mouse()`](../termion/struct.RawTerminal.html#method.with_mouse) method. +#[cfg(not(target_os = "redox"))] +pub struct MouseTerminal { + term: RawTerminal, +} + +#[cfg(not(target_os = "redox"))] +impl Drop for MouseTerminal { + fn drop(&mut self) { + self.term.write(EXIT_MOUSE_SEQUENCE).unwrap(); + } +} + +#[cfg(not(target_os = "redox"))] +impl Deref for MouseTerminal { + type Target = W; + + fn deref(&self) -> &W { + self.term.deref() + } +} + +#[cfg(not(target_os = "redox"))] +impl DerefMut for MouseTerminal { + fn deref_mut(&mut self) -> &mut W { + self.term.deref_mut() + } +} + +#[cfg(not(target_os = "redox"))] +impl Write for MouseTerminal { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.term.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.term.flush() + } +} + + #[cfg(test)] mod test { use super::*; @@ -116,4 +179,10 @@ mod test { out.write(b"this is a test, muahhahahah").unwrap(); } + + #[test] + fn test_enable_mouse() { + let mut out = stdout().into_raw_mode().unwrap().with_mouse().unwrap(); + out.write(b"abcde\x1B[<1;1;0;Mfgh").unwrap(); + } } diff --git a/src/scroll.rs b/src/scroll.rs new file mode 100644 index 0000000..5bdf4d7 --- /dev/null +++ b/src/scroll.rs @@ -0,0 +1,19 @@ +/// Scroll up. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Up(pub u16); + +impl fmt::Display for Up { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, csi!("{}S"), self.0) + } +} + +/// Scroll down. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Down(pub u16); + +impl fmt::Display for Down { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, csi!("{}T"), self.0) + } +}