diff options
Diffstat (limited to 'src')
37 files changed, 0 insertions, 19304 deletions
diff --git a/src/ansi.rs b/src/ansi.rs deleted file mode 100644 index c0ebb79c..00000000 --- a/src/ansi.rs +++ /dev/null @@ -1,1568 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! ANSI Terminal Stream Parsing -use std::io; -use std::ops::Range; -use std::str; - -use crate::index::{Column, Contains, Line}; -use base64; -use glutin::MouseCursor; -use vte; - -use crate::term::color::Rgb; - -// Parse color arguments -// -// Expect that color argument looks like "rgb:xx/xx/xx" or "#xxxxxx" -fn parse_rgb_color(color: &[u8]) -> Option<Rgb> { - let mut iter = color.iter(); - - macro_rules! next { - () => { - iter.next().map(|v| *v as char) - }; - } - - macro_rules! parse_hex { - () => {{ - let mut digit: u8 = 0; - let next = next!().and_then(|v| v.to_digit(16)); - if let Some(value) = next { - digit = value as u8; - } - - let next = next!().and_then(|v| v.to_digit(16)); - if let Some(value) = next { - digit <<= 4; - digit += value as u8; - } - digit - }}; - } - - match next!() { - Some('r') => { - if next!() != Some('g') { - return None; - } - if next!() != Some('b') { - return None; - } - if next!() != Some(':') { - return None; - } - - let r = parse_hex!(); - let val = next!(); - if val != Some('/') { - return None; - } - let g = parse_hex!(); - if next!() != Some('/') { - return None; - } - let b = parse_hex!(); - - Some(Rgb { r, g, b }) - }, - Some('#') => Some(Rgb { r: parse_hex!(), g: parse_hex!(), b: parse_hex!() }), - _ => None, - } -} - -fn parse_number(input: &[u8]) -> Option<u8> { - if input.is_empty() { - return None; - } - let mut num: u8 = 0; - for c in input { - let c = *c as char; - if let Some(digit) = c.to_digit(10) { - num = match num.checked_mul(10).and_then(|v| v.checked_add(digit as u8)) { - Some(v) => v, - None => return None, - } - } else { - return None; - } - } - Some(num) -} - -/// The processor wraps a `vte::Parser` to ultimately call methods on a Handler -pub struct Processor { - state: ProcessorState, - parser: vte::Parser, -} - -/// Internal state for VTE processor -struct ProcessorState { - preceding_char: Option<char>, -} - -/// Helper type that implements `vte::Perform`. -/// -/// Processor creates a Performer when running advance and passes the Performer -/// to `vte::Parser`. -struct Performer<'a, H: Handler + TermInfo, W: io::Write> { - _state: &'a mut ProcessorState, - handler: &'a mut H, - writer: &'a mut W, -} - -impl<'a, H: Handler + TermInfo + 'a, W: io::Write> Performer<'a, H, W> { - /// Create a performer - #[inline] - pub fn new<'b>( - state: &'b mut ProcessorState, - handler: &'b mut H, - writer: &'b mut W, - ) -> Performer<'b, H, W> { - Performer { _state: state, handler, writer } - } -} - -impl Default for Processor { - fn default() -> Processor { - Processor { state: ProcessorState { preceding_char: None }, parser: vte::Parser::new() } - } -} - -impl Processor { - pub fn new() -> Processor { - Default::default() - } - - #[inline] - pub fn advance<H, W>(&mut self, handler: &mut H, byte: u8, writer: &mut W) - where - H: Handler + TermInfo, - W: io::Write, - { - let mut performer = Performer::new(&mut self.state, handler, writer); - self.parser.advance(&mut performer, byte); - } -} - -/// Trait that provides properties of terminal -pub trait TermInfo { - fn lines(&self) -> Line; - fn cols(&self) -> Column; -} - -/// Type that handles actions from the parser -/// -/// XXX Should probably not provide default impls for everything, but it makes -/// writing specific handler impls for tests far easier. -pub trait Handler { - /// OSC to set window title - fn set_title(&mut self, _: &str) {} - - /// Set the window's mouse cursor - fn set_mouse_cursor(&mut self, _: MouseCursor) {} - - /// Set the cursor style - fn set_cursor_style(&mut self, _: Option<CursorStyle>) {} - - /// A character to be displayed - fn input(&mut self, _c: char) {} - - /// Set cursor to position - fn goto(&mut self, _: Line, _: Column) {} - - /// Set cursor to specific row - fn goto_line(&mut self, _: Line) {} - - /// Set cursor to specific column - fn goto_col(&mut self, _: Column) {} - - /// Insert blank characters in current line starting from cursor - fn insert_blank(&mut self, _: Column) {} - - /// Move cursor up `rows` - fn move_up(&mut self, _: Line) {} - - /// Move cursor down `rows` - fn move_down(&mut self, _: Line) {} - - /// Identify the terminal (should write back to the pty stream) - /// - /// TODO this should probably return an io::Result - fn identify_terminal<W: io::Write>(&mut self, _: &mut W) {} - - // Report device status - fn device_status<W: io::Write>(&mut self, _: &mut W, _: usize) {} - - /// Move cursor forward `cols` - fn move_forward(&mut self, _: Column) {} - - /// Move cursor backward `cols` - fn move_backward(&mut self, _: Column) {} - - /// Move cursor down `rows` and set to column 1 - fn move_down_and_cr(&mut self, _: Line) {} - - /// Move cursor up `rows` and set to column 1 - fn move_up_and_cr(&mut self, _: Line) {} - - /// Put `count` tabs - fn put_tab(&mut self, _count: i64) {} - - /// Backspace `count` characters - fn backspace(&mut self) {} - - /// Carriage return - fn carriage_return(&mut self) {} - - /// Linefeed - fn linefeed(&mut self) {} - - /// Ring the bell - /// - /// Hopefully this is never implemented - fn bell(&mut self) {} - - /// Substitute char under cursor - fn substitute(&mut self) {} - - /// Newline - fn newline(&mut self) {} - - /// Set current position as a tabstop - fn set_horizontal_tabstop(&mut self) {} - - /// Scroll up `rows` rows - fn scroll_up(&mut self, _: Line) {} - - /// Scroll down `rows` rows - fn scroll_down(&mut self, _: Line) {} - - /// Insert `count` blank lines - fn insert_blank_lines(&mut self, _: Line) {} - - /// Delete `count` lines - fn delete_lines(&mut self, _: Line) {} - - /// Erase `count` chars in current line following cursor - /// - /// Erase means resetting to the default state (default colors, no content, - /// no mode flags) - fn erase_chars(&mut self, _: Column) {} - - /// Delete `count` chars - /// - /// Deleting a character is like the delete key on the keyboard - everything - /// to the right of the deleted things is shifted left. - fn delete_chars(&mut self, _: Column) {} - - /// Move backward `count` tabs - fn move_backward_tabs(&mut self, _count: i64) {} - - /// Move forward `count` tabs - fn move_forward_tabs(&mut self, _count: i64) {} - - /// Save current cursor position - fn save_cursor_position(&mut self) {} - - /// Restore cursor position - fn restore_cursor_position(&mut self) {} - - /// Clear current line - fn clear_line(&mut self, _mode: LineClearMode) {} - - /// Clear screen - fn clear_screen(&mut self, _mode: ClearMode) {} - - /// Clear tab stops - fn clear_tabs(&mut self, _mode: TabulationClearMode) {} - - /// Reset terminal state - fn reset_state(&mut self) {} - - /// Reverse Index - /// - /// Move the active position to the same horizontal position on the - /// preceding line. If the active position is at the top margin, a scroll - /// down is performed - fn reverse_index(&mut self) {} - - /// set a terminal attribute - fn terminal_attribute(&mut self, _attr: Attr) {} - - /// Set mode - fn set_mode(&mut self, _mode: Mode) {} - - /// Unset mode - fn unset_mode(&mut self, _: Mode) {} - - /// DECSTBM - Set the terminal scrolling region - fn set_scrolling_region(&mut self, _: Range<Line>) {} - - /// DECKPAM - Set keypad to applications mode (ESCape instead of digits) - fn set_keypad_application_mode(&mut self) {} - - /// DECKPNM - Set keypad to numeric mode (digits instead of ESCape seq) - fn unset_keypad_application_mode(&mut self) {} - - /// Set one of the graphic character sets, G0 to G3, as the active charset. - /// - /// 'Invoke' one of G0 to G3 in the GL area. Also referred to as shift in, - /// shift out and locking shift depending on the set being activated - fn set_active_charset(&mut self, _: CharsetIndex) {} - - /// Assign a graphic character set to G0, G1, G2 or G3 - /// - /// 'Designate' a graphic character set as one of G0 to G3, so that it can - /// later be 'invoked' by `set_active_charset` - fn configure_charset(&mut self, _: CharsetIndex, _: StandardCharset) {} - - /// Set an indexed color value - fn set_color(&mut self, _: usize, _: Rgb) {} - - /// Reset an indexed color to original value - fn reset_color(&mut self, _: usize) {} - - /// Set the clipboard - fn set_clipboard(&mut self, _: &str) {} - - /// Run the dectest routine - fn dectest(&mut self) {} -} - -/// Describes shape of cursor -#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)] -pub enum CursorStyle { - /// Cursor is a block like `▒` - Block, - - /// Cursor is an underscore like `_` - Underline, - - /// Cursor is a vertical bar `⎸` - Beam, - - /// Cursor is a box like `☐` - HollowBlock, - - /// Invisible cursor - Hidden, -} - -impl Default for CursorStyle { - fn default() -> CursorStyle { - CursorStyle::Block - } -} - -/// Terminal modes -#[derive(Debug, Eq, PartialEq)] -pub enum Mode { - /// ?1 - CursorKeys = 1, - /// Select 80 or 132 columns per page - /// - /// CSI ? 3 h -> set 132 column font - /// CSI ? 3 l -> reset 80 column font - /// - /// Additionally, - /// - /// * set margins to default positions - /// * erases all data in page memory - /// * resets DECLRMM to unavailable - /// * clears data from the status line (if set to host-writable) - DECCOLM = 3, - /// IRM Insert Mode - /// - /// NB should be part of non-private mode enum - /// - /// * `CSI 4 h` change to insert mode - /// * `CSI 4 l` reset to replacement mode - Insert = 4, - /// ?6 - Origin = 6, - /// ?7 - LineWrap = 7, - /// ?12 - BlinkingCursor = 12, - /// 20 - /// - /// NB This is actually a private mode. We should consider adding a second - /// enumeration for public/private modesets. - LineFeedNewLine = 20, - /// ?25 - ShowCursor = 25, - /// ?1000 - ReportMouseClicks = 1000, - /// ?1002 - ReportCellMouseMotion = 1002, - /// ?1003 - ReportAllMouseMotion = 1003, - /// ?1004 - ReportFocusInOut = 1004, - /// ?1006 - SgrMouse = 1006, - /// ?1049 - SwapScreenAndSetRestoreCursor = 1049, - /// ?2004 - BracketedPaste = 2004, -} - -impl Mode { - /// Create mode from a primitive - /// - /// TODO lots of unhandled values.. - pub fn from_primitive(private: bool, num: i64) -> Option<Mode> { - if private { - Some(match num { - 1 => Mode::CursorKeys, - 3 => Mode::DECCOLM, - 6 => Mode::Origin, - 7 => Mode::LineWrap, - 12 => Mode::BlinkingCursor, - 25 => Mode::ShowCursor, - 1000 => Mode::ReportMouseClicks, - 1002 => Mode::ReportCellMouseMotion, - 1003 => Mode::ReportAllMouseMotion, - 1004 => Mode::ReportFocusInOut, - 1006 => Mode::SgrMouse, - 1049 => Mode::SwapScreenAndSetRestoreCursor, - 2004 => Mode::BracketedPaste, - _ => { - trace!("[unimplemented] primitive mode: {}", num); - return None; - }, - }) - } else { - Some(match num { - 4 => Mode::Insert, - 20 => Mode::LineFeedNewLine, - _ => return None, - }) - } - } -} - -/// Mode for clearing line -/// -/// Relative to cursor -#[derive(Debug)] -pub enum LineClearMode { - /// Clear right of cursor - Right, - /// Clear left of cursor - Left, - /// Clear entire line - All, -} - -/// Mode for clearing terminal -/// -/// Relative to cursor -#[derive(Debug)] -pub enum ClearMode { - /// Clear below cursor - Below, - /// Clear above cursor - Above, - /// Clear entire terminal - All, - /// Clear 'saved' lines (scrollback) - Saved, -} - -/// Mode for clearing tab stops -#[derive(Debug)] -pub enum TabulationClearMode { - /// Clear stop under cursor - Current, - /// Clear all stops - All, -} - -/// Standard colors -/// -/// The order here matters since the enum should be castable to a `usize` for -/// indexing a color list. -#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] -pub enum NamedColor { - /// Black - Black = 0, - /// Red - Red, - /// Green - Green, - /// Yellow - Yellow, - /// Blue - Blue, - /// Magenta - Magenta, - /// Cyan - Cyan, - /// White - White, - /// Bright black - BrightBlack, - /// Bright red - BrightRed, - /// Bright green - BrightGreen, - /// Bright yellow - BrightYellow, - /// Bright blue - BrightBlue, - /// Bright magenta - BrightMagenta, - /// Bright cyan - BrightCyan, - /// Bright white - BrightWhite, - /// The foreground color - Foreground = 256, - /// The background color - Background, - /// Color for the text under the cursor - CursorText, - /// Color for the cursor itself - Cursor, - /// Dim black - DimBlack, - /// Dim red - DimRed, - /// Dim green - DimGreen, - /// Dim yellow - DimYellow, - /// Dim blue - DimBlue, - /// Dim magenta - DimMagenta, - /// Dim cyan - DimCyan, - /// Dim white - DimWhite, - /// The bright foreground color - BrightForeground, - /// Dim foreground - DimForeground, -} - -impl NamedColor { - pub fn to_bright(self) -> Self { - match self { - NamedColor::Foreground => NamedColor::BrightForeground, - NamedColor::Black => NamedColor::BrightBlack, - NamedColor::Red => NamedColor::BrightRed, - NamedColor::Green => NamedColor::BrightGreen, - NamedColor::Yellow => NamedColor::BrightYellow, - NamedColor::Blue => NamedColor::BrightBlue, - NamedColor::Magenta => NamedColor::BrightMagenta, - NamedColor::Cyan => NamedColor::BrightCyan, - NamedColor::White => NamedColor::BrightWhite, - NamedColor::DimForeground => NamedColor::Foreground, - NamedColor::DimBlack => NamedColor::Black, - NamedColor::DimRed => NamedColor::Red, - NamedColor::DimGreen => NamedColor::Green, - NamedColor::DimYellow => NamedColor::Yellow, - NamedColor::DimBlue => NamedColor::Blue, - NamedColor::DimMagenta => NamedColor::Magenta, - NamedColor::DimCyan => NamedColor::Cyan, - NamedColor::DimWhite => NamedColor::White, - val => val, - } - } - - pub fn to_dim(self) -> Self { - match self { - NamedColor::Black => NamedColor::DimBlack, - NamedColor::Red => NamedColor::DimRed, - NamedColor::Green => NamedColor::DimGreen, - NamedColor::Yellow => NamedColor::DimYellow, - NamedColor::Blue => NamedColor::DimBlue, - NamedColor::Magenta => NamedColor::DimMagenta, - NamedColor::Cyan => NamedColor::DimCyan, - NamedColor::White => NamedColor::DimWhite, - NamedColor::Foreground => NamedColor::DimForeground, - NamedColor::BrightBlack => NamedColor::Black, - NamedColor::BrightRed => NamedColor::Red, - NamedColor::BrightGreen => NamedColor::Green, - NamedColor::BrightYellow => NamedColor::Yellow, - NamedColor::BrightBlue => NamedColor::Blue, - NamedColor::BrightMagenta => NamedColor::Magenta, - NamedColor::BrightCyan => NamedColor::Cyan, - NamedColor::BrightWhite => NamedColor::White, - NamedColor::BrightForeground => NamedColor::Foreground, - val => val, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum Color { - Named(NamedColor), - Spec(Rgb), - Indexed(u8), -} - -/// Terminal character attributes -#[derive(Debug, Eq, PartialEq)] -pub enum Attr { - /// Clear all special abilities - Reset, - /// Bold text - Bold, - /// Dim or secondary color - Dim, - /// Italic text - Italic, - /// Underscore text - Underscore, - /// Blink cursor slowly - BlinkSlow, - /// Blink cursor fast - BlinkFast, - /// Invert colors - Reverse, - /// Do not display characters - Hidden, - /// Strikeout text - Strike, - /// Cancel bold - CancelBold, - /// Cancel bold and dim - CancelBoldDim, - /// Cancel italic - CancelItalic, - /// Cancel underline - CancelUnderline, - /// Cancel blink - CancelBlink, - /// Cancel inversion - CancelReverse, - /// Cancel text hiding - CancelHidden, - /// Cancel strikeout - CancelStrike, - /// Set indexed foreground color - Foreground(Color), - /// Set indexed background color - Background(Color), -} - -/// Identifiers which can be assigned to a graphic character set -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum CharsetIndex { - /// Default set, is designated as ASCII at startup - G0, - G1, - G2, - G3, -} - -impl Default for CharsetIndex { - fn default() -> Self { - CharsetIndex::G0 - } -} - -/// Standard or common character sets which can be designated as G0-G3 -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum StandardCharset { - Ascii, - SpecialCharacterAndLineDrawing, -} - -impl Default for StandardCharset { - fn default() -> Self { - StandardCharset::Ascii - } -} - -impl<'a, H, W> vte::Perform for Performer<'a, H, W> -where - H: Handler + TermInfo + 'a, - W: io::Write + 'a, -{ - #[inline] - fn print(&mut self, c: char) { - self.handler.input(c); - self._state.preceding_char = Some(c); - } - - #[inline] - fn execute(&mut self, byte: u8) { - match byte { - C0::HT => self.handler.put_tab(1), - C0::BS => self.handler.backspace(), - C0::CR => self.handler.carriage_return(), - C0::LF | C0::VT | C0::FF => self.handler.linefeed(), - C0::BEL => self.handler.bell(), - C0::SUB => self.handler.substitute(), - C0::SI => self.handler.set_active_charset(CharsetIndex::G0), - C0::SO => self.handler.set_active_charset(CharsetIndex::G1), - C1::NEL => self.handler.newline(), - C1::HTS => self.handler.set_horizontal_tabstop(), - C1::DECID => self.handler.identify_terminal(self.writer), - _ => debug!("[unhandled] execute byte={:02x}", byte), - } - } - - #[inline] - fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool) { - debug!( - "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}", - params, intermediates, ignore - ); - } - - #[inline] - fn put(&mut self, byte: u8) { - debug!("[unhandled put] byte={:?}", byte); - } - - #[inline] - fn unhook(&mut self) { - debug!("[unhandled unhook]"); - } - - // TODO replace OSC parsing with parser combinators - #[inline] - fn osc_dispatch(&mut self, params: &[&[u8]]) { - fn unhandled(params: &[&[u8]]) { - let mut buf = String::new(); - for items in params { - buf.push_str("["); - for item in *items { - buf.push_str(&format!("{:?},", *item as char)); - } - buf.push_str("],"); - } - debug!("[unhandled osc_dispatch]: [{}] at line {}", &buf, line!()); - } - - if params.is_empty() || params[0].is_empty() { - return; - } - - match params[0] { - // Set window title - b"0" | b"2" => { - if params.len() >= 2 { - if let Ok(utf8_title) = str::from_utf8(params[1]) { - self.handler.set_title(utf8_title); - return; - } - } - unhandled(params); - }, - - // Set icon name - // This is ignored, since alacritty has no concept of tabs - b"1" => return, - - // Set color index - b"4" => { - if params.len() > 1 && params.len() % 2 != 0 { - for chunk in params[1..].chunks(2) { - let index = parse_number(chunk[0]); - let color = parse_rgb_color(chunk[1]); - if let (Some(i), Some(c)) = (index, color) { - self.handler.set_color(i as usize, c); - return; - } - } - } - unhandled(params); - }, - - // Set foreground color - b"10" => { - if params.len() >= 2 { - if let Some(color) = parse_rgb_color(params[1]) { - self.handler.set_color(NamedColor::Foreground as usize, color); - return; - } - } - unhandled(params); - }, - - // Set background color - b"11" => { - if params.len() >= 2 { - if let Some(color) = parse_rgb_color(params[1]) { - self.handler.set_color(NamedColor::Background as usize, color); - return; - } - } - unhandled(params); - }, - - // Set text cursor color - b"12" => { - if params.len() >= 2 { - if let Some(color) = parse_rgb_color(params[1]) { - self.handler.set_color(NamedColor::Cursor as usize, color); - return; - } - } - unhandled(params); - }, - - // Set cursor style - b"50" => { - if params.len() >= 2 - && params[1].len() >= 13 - && params[1][0..12] == *b"CursorShape=" - { - let style = match params[1][12] as char { - '0' => CursorStyle::Block, - '1' => CursorStyle::Beam, - '2' => CursorStyle::Underline, - _ => return unhandled(params), - }; - self.handler.set_cursor_style(Some(style)); - return; - } - unhandled(params); - }, - - // Set clipboard - b"52" => { - if params.len() < 3 { - return unhandled(params); - } - - match params[2] { - b"?" => unhandled(params), - selection => { - if let Ok(string) = base64::decode(selection) { - if let Ok(utf8_string) = str::from_utf8(&string) { - self.handler.set_clipboard(utf8_string); - } - } - }, - } - }, - - // Reset color index - b"104" => { - // Reset all color indexes when no parameters are given - if params.len() == 1 { - for i in 0..256 { - self.handler.reset_color(i); - } - return; - } - - // Reset color indexes given as parameters - for param in ¶ms[1..] { - match parse_number(param) { - Some(index) => self.handler.reset_color(index as usize), - None => unhandled(params), - } - } - }, - - // Reset foreground color - b"110" => self.handler.reset_color(NamedColor::Foreground as usize), - - // Reset background color - b"111" => self.handler.reset_color(NamedColor::Background as usize), - - // Reset text cursor color - b"112" => self.handler.reset_color(NamedColor::Cursor as usize), - - _ => unhandled(params), - } - } - - #[inline] - fn csi_dispatch(&mut self, args: &[i64], intermediates: &[u8], _ignore: bool, action: char) { - let private = intermediates.get(0).map(|b| *b == b'?').unwrap_or(false); - let handler = &mut self.handler; - let writer = &mut self.writer; - - macro_rules! unhandled { - () => {{ - debug!( - "[Unhandled CSI] action={:?}, args={:?}, intermediates={:?}", - action, args, intermediates - ); - return; - }}; - } - - macro_rules! arg_or_default { - (idx: $idx:expr, default: $default:expr) => { - args.get($idx) - .and_then(|v| if *v == 0 { None } else { Some(*v) }) - .unwrap_or($default) - }; - } - - match action { - '@' => handler.insert_blank(Column(arg_or_default!(idx: 0, default: 1) as usize)), - 'A' => { - handler.move_up(Line(arg_or_default!(idx: 0, default: 1) as usize)); - }, - 'b' => { - if let Some(c) = self._state.preceding_char { - for _ in 0..arg_or_default!(idx: 0, default: 1) { - handler.input(c); - } - } else { - debug!("tried to repeat with no preceding char"); - } - }, - 'B' | 'e' => handler.move_down(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'c' => handler.identify_terminal(writer), - 'C' | 'a' => handler.move_forward(Column(arg_or_default!(idx: 0, default: 1) as usize)), - 'D' => handler.move_backward(Column(arg_or_default!(idx: 0, default: 1) as usize)), - 'E' => handler.move_down_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'F' => handler.move_up_and_cr(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'g' => { - let mode = match arg_or_default!(idx: 0, default: 0) { - 0 => TabulationClearMode::Current, - 3 => TabulationClearMode::All, - _ => unhandled!(), - }; - - handler.clear_tabs(mode); - }, - 'G' | '`' => handler.goto_col(Column(arg_or_default!(idx: 0, default: 1) as usize - 1)), - 'H' | 'f' => { - let y = arg_or_default!(idx: 0, default: 1) as usize; - let x = arg_or_default!(idx: 1, default: 1) as usize; - handler.goto(Line(y - 1), Column(x - 1)); - }, - 'I' => handler.move_forward_tabs(arg_or_default!(idx: 0, default: 1)), - 'J' => { - let mode = match arg_or_default!(idx: 0, default: 0) { - 0 => ClearMode::Below, - 1 => ClearMode::Above, - 2 => ClearMode::All, - 3 => ClearMode::Saved, - _ => unhandled!(), - }; - - handler.clear_screen(mode); - }, - 'K' => { - let mode = match arg_or_default!(idx: 0, default: 0) { - 0 => LineClearMode::Right, - 1 => LineClearMode::Left, - 2 => LineClearMode::All, - _ => unhandled!(), - }; - - handler.clear_line(mode); - }, - 'S' => handler.scroll_up(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'T' => handler.scroll_down(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'L' => handler.insert_blank_lines(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'l' => { - for arg in args { - let mode = Mode::from_primitive(private, *arg); - match mode { - Some(mode) => handler.unset_mode(mode), - None => unhandled!(), - } - } - }, - 'M' => handler.delete_lines(Line(arg_or_default!(idx: 0, default: 1) as usize)), - 'X' => handler.erase_chars(Column(arg_or_default!(idx: 0, default: 1) as usize)), - 'P' => handler.delete_chars(Column(arg_or_default!(idx: 0, default: 1) as usize)), - 'Z' => handler.move_backward_tabs(arg_or_default!(idx: 0, default: 1)), - 'd' => handler.goto_line(Line(arg_or_default!(idx: 0, default: 1) as usize - 1)), - 'h' => { - for arg in args { - let mode = Mode::from_primitive(private, *arg); - match mode { - Some(mode) => handler.set_mode(mode), - None => unhandled!(), - } - } - }, - 'm' => { - // Sometimes a C-style for loop is just what you need - let mut i = 0; // C-for initializer - if args.is_empty() { - handler.terminal_attribute(Attr::Reset); - return; - } - loop { - if i >= args.len() { - // C-for condition - break; - } - - let attr = match args[i] { - 0 => Attr::Reset, - 1 => Attr::Bold, - 2 => Attr::Dim, - 3 => Attr::Italic, - 4 => Attr::Underscore, - 5 => Attr::BlinkSlow, - 6 => Attr::BlinkFast, - 7 => Attr::Reverse, - 8 => Attr::Hidden, - 9 => Attr::Strike, - 21 => Attr::CancelBold, - 22 => Attr::CancelBoldDim, - 23 => Attr::CancelItalic, - 24 => Attr::CancelUnderline, - 25 => Attr::CancelBlink, - 27 => Attr::CancelReverse, - 28 => Attr::CancelHidden, - 29 => Attr::CancelStrike, - 30 => Attr::Foreground(Color::Named(NamedColor::Black)), - 31 => Attr::Foreground(Color::Named(NamedColor::Red)), - 32 => Attr::Foreground(Color::Named(NamedColor::Green)), - 33 => Attr::Foreground(Color::Named(NamedColor::Yellow)), - 34 => Attr::Foreground(Color::Named(NamedColor::Blue)), - 35 => Attr::Foreground(Color::Named(NamedColor::Magenta)), - 36 => Attr::Foreground(Color::Named(NamedColor::Cyan)), - 37 => Attr::Foreground(Color::Named(NamedColor::White)), - 38 => { - let mut start = 0; - if let Some(color) = parse_color(&args[i..], &mut start) { - i += start; - Attr::Foreground(color) - } else { - break; - } - }, - 39 => Attr::Foreground(Color::Named(NamedColor::Foreground)), - 40 => Attr::Background(Color::Named(NamedColor::Black)), - 41 => Attr::Background(Color::Named(NamedColor::Red)), - 42 => Attr::Background(Color::Named(NamedColor::Green)), - 43 => Attr::Background(Color::Named(NamedColor::Yellow)), - 44 => Attr::Background(Color::Named(NamedColor::Blue)), - 45 => Attr::Background(Color::Named(NamedColor::Magenta)), - 46 => Attr::Background(Color::Named(NamedColor::Cyan)), - 47 => Attr::Background(Color::Named(NamedColor::White)), - 48 => { - let mut start = 0; - if let Some(color) = parse_color(&args[i..], &mut start) { - i += start; - Attr::Background(color) - } else { - break; - } - }, - 49 => Attr::Background(Color::Named(NamedColor::Background)), - 90 => Attr::Foreground(Color::Named(NamedColor::BrightBlack)), - 91 => Attr::Foreground(Color::Named(NamedColor::BrightRed)), - 92 => Attr::Foreground(Color::Named(NamedColor::BrightGreen)), - 93 => Attr::Foreground(Color::Named(NamedColor::BrightYellow)), - 94 => Attr::Foreground(Color::Named(NamedColor::BrightBlue)), - 95 => Attr::Foreground(Color::Named(NamedColor::BrightMagenta)), - 96 => Attr::Foreground(Color::Named(NamedColor::BrightCyan)), - 97 => Attr::Foreground(Color::Named(NamedColor::BrightWhite)), - 100 => Attr::Background(Color::Named(NamedColor::BrightBlack)), - 101 => Attr::Background(Color::Named(NamedColor::BrightRed)), - 102 => Attr::Background(Color::Named(NamedColor::BrightGreen)), - 103 => Attr::Background(Color::Named(NamedColor::BrightYellow)), - 104 => Attr::Background(Color::Named(NamedColor::BrightBlue)), - 105 => Attr::Background(Color::Named(NamedColor::BrightMagenta)), - 106 => Attr::Background(Color::Named(NamedColor::BrightCyan)), - 107 => Attr::Background(Color::Named(NamedColor::BrightWhite)), - _ => unhandled!(), - }; - - handler.terminal_attribute(attr); - - i += 1; // C-for expr - } - }, - 'n' => handler.device_status(writer, arg_or_default!(idx: 0, default: 0) as usize), - 'r' => { - if private { - unhandled!(); - } - let arg0 = arg_or_default!(idx: 0, default: 1) as usize; - let top = Line(arg0 - 1); - // Bottom should be included in the range, but range end is not - // usually included. One option would be to use an inclusive - // range, but instead we just let the open range end be 1 - // higher. - let arg1 = arg_or_default!(idx: 1, default: handler.lines().0 as _) as usize; - let bottom = Line(arg1); - - handler.set_scrolling_region(top..bottom); - }, - 's' => handler.save_cursor_position(), - 'u' => handler.restore_cursor_position(), - 'q' => { - let style = match arg_or_default!(idx: 0, default: 0) { - 0 => None, - 1 | 2 => Some(CursorStyle::Block), - 3 | 4 => Some(CursorStyle::Underline), - 5 | 6 => Some(CursorStyle::Beam), - _ => unhandled!(), - }; - - handler.set_cursor_style(style); - }, - _ => unhandled!(), - } - } - - #[inline] - fn esc_dispatch(&mut self, params: &[i64], intermediates: &[u8], _ignore: bool, byte: u8) { - macro_rules! unhandled { - () => {{ - debug!( - "[unhandled] esc_dispatch params={:?}, ints={:?}, byte={:?} ({:02x})", - params, intermediates, byte as char, byte - ); - return; - }}; - } - - macro_rules! configure_charset { - ($charset:path) => {{ - let index: CharsetIndex = match intermediates.first().cloned() { - Some(b'(') => CharsetIndex::G0, - Some(b')') => CharsetIndex::G1, - Some(b'*') => CharsetIndex::G2, - Some(b'+') => CharsetIndex::G3, - _ => unhandled!(), - }; - self.handler.configure_charset(index, $charset) - }}; - } - - match byte { - b'B' => configure_charset!(StandardCharset::Ascii), - b'D' => self.handler.linefeed(), - b'E' => { - self.handler.linefeed(); - self.handler.carriage_return(); - }, - b'H' => self.handler.set_horizontal_tabstop(), - b'M' => self.handler.reverse_index(), - b'Z' => self.handler.identify_terminal(self.writer), - b'c' => self.handler.reset_state(), - b'0' => configure_charset!(StandardCharset::SpecialCharacterAndLineDrawing), - b'7' => self.handler.save_cursor_position(), - b'8' => { - if !intermediates.is_empty() && intermediates[0] == b'#' { - self.handler.dectest(); - } else { - self.handler.restore_cursor_position(); - } - }, - b'=' => self.handler.set_keypad_application_mode(), - b'>' => self.handler.unset_keypad_application_mode(), - b'\\' => (), // String terminator, do nothing (parser handles as string terminator) - _ => unhandled!(), - } - } -} - -/// Parse a color specifier from list of attributes -fn parse_color(attrs: &[i64], i: &mut usize) -> Option<Color> { - if attrs.len() < 2 { - return None; - } - - match attrs[*i + 1] { - 2 => { - // RGB color spec - if attrs.len() < 5 { - debug!("Expected RGB color spec; got {:?}", attrs); - return None; - } - - let r = attrs[*i + 2]; - let g = attrs[*i + 3]; - let b = attrs[*i + 4]; - - *i += 4; - - let range = 0..256; - if !range.contains_(r) || !range.contains_(g) || !range.contains_(b) { - debug!("Invalid RGB color spec: ({}, {}, {})", r, g, b); - return None; - } - - Some(Color::Spec(Rgb { r: r as u8, g: g as u8, b: b as u8 })) - }, - 5 => { - if attrs.len() < 3 { - debug!("Expected color index; got {:?}", attrs); - None - } else { - *i += 2; - let idx = attrs[*i]; - match idx { - 0..=255 => Some(Color::Indexed(idx as u8)), - _ => { - debug!("Invalid color index: {}", idx); - None - }, - } - } - }, - _ => { - debug!("Unexpected color attr: {}", attrs[*i + 1]); - None - }, - } -} - -/// C0 set of 7-bit control characters (from ANSI X3.4-1977). -#[allow(non_snake_case)] -pub mod C0 { - /// Null filler, terminal should ignore this character - pub const NUL: u8 = 0x00; - /// Start of Header - pub const SOH: u8 = 0x01; - /// Start of Text, implied end of header - pub const STX: u8 = 0x02; - /// End of Text, causes some terminal to respond with ACK or NAK - pub const ETX: u8 = 0x03; - /// End of Transmission - pub const EOT: u8 = 0x04; - /// Enquiry, causes terminal to send ANSWER-BACK ID - pub const ENQ: u8 = 0x05; - /// Acknowledge, usually sent by terminal in response to ETX - pub const ACK: u8 = 0x06; - /// Bell, triggers the bell, buzzer, or beeper on the terminal - pub const BEL: u8 = 0x07; - /// Backspace, can be used to define overstruck characters - pub const BS: u8 = 0x08; - /// Horizontal Tabulation, move to next predetermined position - pub const HT: u8 = 0x09; - /// Linefeed, move to same position on next line (see also NL) - pub const LF: u8 = 0x0A; - /// Vertical Tabulation, move to next predetermined line - pub const VT: u8 = 0x0B; - /// Form Feed, move to next form or page - pub const FF: u8 = 0x0C; - /// Carriage Return, move to first character of current line - pub const CR: u8 = 0x0D; - /// Shift Out, switch to G1 (other half of character set) - pub const SO: u8 = 0x0E; - /// Shift In, switch to G0 (normal half of character set) - pub const SI: u8 = 0x0F; - /// Data Link Escape, interpret next control character specially - pub const DLE: u8 = 0x10; - /// (DC1) Terminal is allowed to resume transmitting - pub const XON: u8 = 0x11; - /// Device Control 2, causes ASR-33 to activate paper-tape reader - pub const DC2: u8 = 0x12; - /// (DC2) Terminal must pause and refrain from transmitting - pub const XOFF: u8 = 0x13; - /// Device Control 4, causes ASR-33 to deactivate paper-tape reader - pub const DC4: u8 = 0x14; - /// Negative Acknowledge, used sometimes with ETX and ACK - pub const NAK: u8 = 0x15; - /// Synchronous Idle, used to maintain timing in Sync communication - pub const SYN: u8 = 0x16; - /// End of Transmission block - pub const ETB: u8 = 0x17; - /// Cancel (makes VT100 abort current escape sequence if any) - pub const CAN: u8 = 0x18; - /// End of Medium - pub const EM: u8 = 0x19; - /// Substitute (VT100 uses this to display parity errors) - pub const SUB: u8 = 0x1A; - /// Prefix to an escape sequence - pub const ESC: u8 = 0x1B; - /// File Separator - pub const FS: u8 = 0x1C; - /// Group Separator - pub const GS: u8 = 0x1D; - /// Record Separator (sent by VT132 in block-transfer mode) - pub const RS: u8 = 0x1E; - /// Unit Separator - pub const US: u8 = 0x1F; - /// Delete, should be ignored by terminal - pub const DEL: u8 = 0x7f; -} - -/// C1 set of 8-bit control characters (from ANSI X3.64-1979) -/// -/// 0x80 (@), 0x81 (A), 0x82 (B), 0x83 (C) are reserved -/// 0x98 (X), 0x99 (Y) are reserved -/// 0x9a (Z) is 'reserved', but causes DEC terminals to respond with DA codes -#[allow(non_snake_case)] -pub mod C1 { - /// Reserved - pub const PAD: u8 = 0x80; - /// Reserved - pub const HOP: u8 = 0x81; - /// Reserved - pub const BPH: u8 = 0x82; - /// Reserved - pub const NBH: u8 = 0x83; - /// Index, moves down one line same column regardless of NL - pub const IND: u8 = 0x84; - /// New line, moves done one line and to first column (CR+LF) - pub const NEL: u8 = 0x85; - /// Start of Selected Area to be sent to auxiliary output device - pub const SSA: u8 = 0x86; - /// End of Selected Area to be sent to auxiliary output device - pub const ESA: u8 = 0x87; - /// Horizontal Tabulation Set at current position - pub const HTS: u8 = 0x88; - /// Hor Tab Justify, moves string to next tab position - pub const HTJ: u8 = 0x89; - /// Vertical Tabulation Set at current line - pub const VTS: u8 = 0x8A; - /// Partial Line Down (subscript) - pub const PLD: u8 = 0x8B; - /// Partial Line Up (superscript) - pub const PLU: u8 = 0x8C; - /// Reverse Index, go up one line, reverse scroll if necessary - pub const RI: u8 = 0x8D; - /// Single Shift to G2 - pub const SS2: u8 = 0x8E; - /// Single Shift to G3 (VT100 uses this for sending PF keys) - pub const SS3: u8 = 0x8F; - /// Device Control String, terminated by ST (VT125 enters graphics) - pub const DCS: u8 = 0x90; - /// Private Use 1 - pub const PU1: u8 = 0x91; - /// Private Use 2 - pub const PU2: u8 = 0x92; - /// Set Transmit State - pub const STS: u8 = 0x93; - /// Cancel character, ignore previous character - pub const CCH: u8 = 0x94; - /// Message Waiting, turns on an indicator on the terminal - pub const MW: u8 = 0x95; - /// Start of Protected Area - pub const SPA: u8 = 0x96; - /// End of Protected Area - pub const EPA: u8 = 0x97; - /// SOS - pub const SOS: u8 = 0x98; - /// SGCI - pub const SGCI: u8 = 0x99; - /// DECID - Identify Terminal - pub const DECID: u8 = 0x9a; - /// Control Sequence Introducer - pub const CSI: u8 = 0x9B; - /// String Terminator (VT125 exits graphics) - pub const ST: u8 = 0x9C; - /// Operating System Command (reprograms intelligent terminal) - pub const OSC: u8 = 0x9D; - /// Privacy Message (password verification), terminated by ST - pub const PM: u8 = 0x9E; - /// Application Program Command (to word processor), term by ST - pub const APC: u8 = 0x9F; -} - -// Tests for parsing escape sequences -// -// Byte sequences used in these tests are recording of pty stdout. -#[cfg(test)] -mod tests { - use super::{ - parse_number, parse_rgb_color, Attr, CharsetIndex, Color, Handler, Processor, - StandardCharset, TermInfo, - }; - use crate::index::{Column, Line}; - use crate::term::color::Rgb; - use std::io; - - /// The /dev/null of `io::Write` - struct Void; - - impl io::Write for Void { - fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { - Ok(bytes.len()) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } - } - - #[derive(Default)] - struct AttrHandler { - attr: Option<Attr>, - } - - impl Handler for AttrHandler { - fn terminal_attribute(&mut self, attr: Attr) { - self.attr = Some(attr); - } - } - - impl TermInfo for AttrHandler { - fn lines(&self) -> Line { - Line(24) - } - - fn cols(&self) -> Column { - Column(80) - } - } - - #[test] - fn parse_control_attribute() { - static BYTES: &'static [u8] = &[0x1b, 0x5b, 0x31, 0x6d]; - - let mut parser = Processor::new(); - let mut handler = AttrHandler::default(); - - for byte in &BYTES[..] { - parser.advance(&mut handler, *byte, &mut Void); - } - - assert_eq!(handler.attr, Some(Attr::Bold)); - } - - #[test] - fn parse_truecolor_attr() { - static BYTES: &'static [u8] = &[ - 0x1b, 0x5b, 0x33, 0x38, 0x3b, 0x32, 0x3b, 0x31, 0x32, 0x38, 0x3b, 0x36, 0x36, 0x3b, - 0x32, 0x35, 0x35, 0x6d, - ]; - - let mut parser = Processor::new(); - let mut handler = AttrHandler::default(); - - for byte in &BYTES[..] { - parser.advance(&mut handler, *byte, &mut Void); - } - - let spec = Rgb { r: 128, g: 66, b: 255 }; - - assert_eq!(handler.attr, Some(Attr::Foreground(Color::Spec(spec)))); - } - - /// No exactly a test; useful for debugging - #[test] - fn parse_zsh_startup() { - static BYTES: &'static [u8] = &[ - 0x1b, 0x5b, 0x31, 0x6d, 0x1b, 0x5b, 0x37, 0x6d, 0x25, 0x1b, 0x5b, 0x32, 0x37, 0x6d, - 0x1b, 0x5b, 0x31, 0x6d, 0x1b, 0x5b, 0x30, 0x6d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x0d, 0x20, 0x0d, 0x0d, 0x1b, 0x5b, 0x30, 0x6d, 0x1b, 0x5b, 0x32, - 0x37, 0x6d, 0x1b, 0x5b, 0x32, 0x34, 0x6d, 0x1b, 0x5b, 0x4a, 0x6a, 0x77, 0x69, 0x6c, - 0x6d, 0x40, 0x6a, 0x77, 0x69, 0x6c, 0x6d, 0x2d, 0x64, 0x65, 0x73, 0x6b, 0x20, 0x1b, - 0x5b, 0x30, 0x31, 0x3b, 0x33, 0x32, 0x6d, 0xe2, 0x9e, 0x9c, 0x20, 0x1b, 0x5b, 0x30, - 0x31, 0x3b, 0x33, 0x32, 0x6d, 0x20, 0x1b, 0x5b, 0x33, 0x36, 0x6d, 0x7e, 0x2f, 0x63, - 0x6f, 0x64, 0x65, - ]; - - let mut handler = AttrHandler::default(); - let mut parser = Processor::new(); - - for byte in &BYTES[..] { - parser.advance(&mut handler, *byte, &mut Void); - } - } - - struct CharsetHandler { - index: CharsetIndex, - charset: StandardCharset, - } - - impl Default for CharsetHandler { - fn default() -> CharsetHandler { - CharsetHandler { index: CharsetIndex::G0, charset: StandardCharset::Ascii } - } - } - - impl Handler for CharsetHandler { - fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) { - self.index = index; - self.charset = charset; - } - - fn set_active_charset(&mut self, index: CharsetIndex) { - self.index = index; - } - } - - impl TermInfo for CharsetHandler { - fn lines(&self) -> Line { - Line(200) - } - - fn cols(&self) -> Column { - Column(90) - } - } - - #[test] - fn parse_designate_g0_as_line_drawing() { - static BYTES: &'static [u8] = &[0x1b, b'(', b'0']; - let mut parser = Processor::new(); - let mut handler = CharsetHandler::default(); - - for byte in &BYTES[..] { - parser.advance(&mut handler, *byte, &mut Void); - } - - assert_eq!(handler.index, CharsetIndex::G0); - assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing); - } - - #[test] - fn parse_designate_g1_as_line_drawing_and_invoke() { - static BYTES: &'static [u8] = &[0x1b, 0x29, 0x30, 0x0e]; - let mut parser = Processor::new(); - let mut handler = CharsetHandler::default(); - - for byte in &BYTES[..3] { - parser.advance(&mut handler, *byte, &mut Void); - } - - assert_eq!(handler.index, CharsetIndex::G1); - assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing); - - let mut handler = CharsetHandler::default(); - parser.advance(&mut handler, BYTES[3], &mut Void); - - assert_eq!(handler.index, CharsetIndex::G1); - } - - #[test] - fn parse_valid_rgb_color() { - assert_eq!(parse_rgb_color(b"rgb:11/aa/ff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff })); - } - - #[test] - fn parse_valid_rgb_color2() { - assert_eq!(parse_rgb_color(b"#11aaff"), Some(Rgb { r: 0x11, g: 0xaa, b: 0xff })); - } - - #[test] - fn parse_invalid_number() { - assert_eq!(parse_number(b"1abc"), None); - } - - #[test] - fn parse_valid_number() { - assert_eq!(parse_number(b"123"), Some(123)); - } - - #[test] - fn parse_number_too_large() { - assert_eq!(parse_number(b"321"), None); - } -} diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index 2cddbc82..00000000 --- a/src/cli.rs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use ::log; -use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; - -use crate::config::{Delta, Dimensions, Shell}; -use crate::index::{Column, Line}; -use crate::window::DEFAULT_NAME; -use std::borrow::Cow; -use std::path::{Path, PathBuf}; - -/// Options specified on the command line -pub struct Options { - pub live_config_reload: Option<bool>, - pub print_events: bool, - pub ref_test: bool, - pub dimensions: Option<Dimensions>, - pub position: Option<Delta<i32>>, - pub title: Option<String>, - pub class: Option<String>, - pub log_level: log::LevelFilter, - pub command: Option<Shell<'static>>, - pub working_dir: Option<PathBuf>, - pub config: Option<PathBuf>, - pub persistent_logging: bool, -} - -impl Default for Options { - fn default() -> Options { - Options { - live_config_reload: None, - print_events: false, - ref_test: false, - dimensions: None, - position: None, - title: None, - class: None, - log_level: log::LevelFilter::Warn, - command: None, - working_dir: None, - config: None, - persistent_logging: false, - } - } -} - -impl Options { - /// Build `Options` from command line arguments - pub fn load() -> Options { - let mut options = Options::default(); - - let matches = App::new(crate_name!()) - .version(crate_version!()) - .author(crate_authors!("\n")) - .about(crate_description!()) - .arg(Arg::with_name("ref-test").long("ref-test").help("Generates ref test")) - .arg( - Arg::with_name("live-config-reload") - .long("live-config-reload") - .help("Enable automatic config reloading"), - ) - .arg( - Arg::with_name("no-live-config-reload") - .long("no-live-config-reload") - .help("Disable automatic config reloading") - .conflicts_with("live-config-reload"), - ) - .arg( - Arg::with_name("print-events") - .long("print-events") - .help("Print all events to stdout"), - ) - .arg( - Arg::with_name("persistent-logging") - .long("persistent-logging") - .help("Keep the log file after quitting Alacritty"), - ) - .arg( - Arg::with_name("dimensions") - .long("dimensions") - .short("d") - .value_names(&["columns", "lines"]) - .help( - "Defines the window dimensions. Falls back to size specified by window \ - manager if set to 0x0 [default: 0x0]", - ), - ) - .arg( - Arg::with_name("position") - .long("position") - .allow_hyphen_values(true) - .value_names(&["x-pos", "y-pos"]) - .help( - "Defines the window position. Falls back to position specified by window \ - manager if unset [default: unset]", - ), - ) - .arg( - Arg::with_name("title") - .long("title") - .short("t") - .takes_value(true) - .help(&format!("Defines the window title [default: {}]", DEFAULT_NAME)), - ) - .arg( - Arg::with_name("class") - .long("class") - .takes_value(true) - .help(&format!("Defines window class on Linux [default: {}]", DEFAULT_NAME)), - ) - .arg( - Arg::with_name("q") - .short("q") - .multiple(true) - .conflicts_with("v") - .help("Reduces the level of verbosity (the min level is -qq)"), - ) - .arg( - Arg::with_name("v") - .short("v") - .multiple(true) - .conflicts_with("q") - .help("Increases the level of verbosity (the max level is -vvv)"), - ) - .arg( - Arg::with_name("working-directory") - .long("working-directory") - .takes_value(true) - .help("Start the shell in the specified working directory"), - ) - .arg(Arg::with_name("config-file").long("config-file").takes_value(true).help( - "Specify alternative configuration file [default: \ - $XDG_CONFIG_HOME/alacritty/alacritty.yml]", - )) - .arg( - Arg::with_name("command") - .long("command") - .short("e") - .multiple(true) - .takes_value(true) - .min_values(1) - .allow_hyphen_values(true) - .help("Command and args to execute (must be last argument)"), - ) - .get_matches(); - - if matches.is_present("ref-test") { - options.ref_test = true; - } - - if matches.is_present("print-events") { - options.print_events = true; - } - - if matches.is_present("live-config-reload") { - options.live_config_reload = Some(true); - } else if matches.is_present("no-live-config-reload") { - options.live_config_reload = Some(false); - } - - if matches.is_present("persistent-logging") { - options.persistent_logging = true; - } - - if let Some(mut dimensions) = matches.values_of("dimensions") { - let width = dimensions.next().map(|w| w.parse().map(Column)); - let height = dimensions.next().map(|h| h.parse().map(Line)); - if let (Some(Ok(width)), Some(Ok(height))) = (width, height) { - options.dimensions = Some(Dimensions::new(width, height)); - } - } - - if let Some(mut position) = matches.values_of("position") { - let x = position.next().map(str::parse); - let y = position.next().map(str::parse); - if let (Some(Ok(x)), Some(Ok(y))) = (x, y) { - options.position = Some(Delta { x, y }); - } - } - - options.class = matches.value_of("class").map(ToOwned::to_owned); - options.title = matches.value_of("title").map(ToOwned::to_owned); - - match matches.occurrences_of("q") { - 0 => {}, - 1 => options.log_level = log::LevelFilter::Error, - 2 | _ => options.log_level = log::LevelFilter::Off, - } - - match matches.occurrences_of("v") { - 0 if !options.print_events => {}, - 0 | 1 => options.log_level = log::LevelFilter::Info, - 2 => options.log_level = log::LevelFilter::Debug, - 3 | _ => options.log_level = log::LevelFilter::Trace, - } - - if let Some(dir) = matches.value_of("working-directory") { - options.working_dir = Some(PathBuf::from(dir.to_string())); - } - - if let Some(path) = matches.value_of("config-file") { - options.config = Some(PathBuf::from(path.to_string())); - } - - if let Some(mut args) = matches.values_of("command") { - // The following unwrap is guaranteed to succeed. - // If 'command' exists it must also have a first item since - // Arg::min_values(1) is set. - let command = String::from(args.next().unwrap()); - let args = args.map(String::from).collect(); - options.command = Some(Shell::new_with_args(command, args)); - } - - options - } - - pub fn dimensions(&self) -> Option<Dimensions> { - self.dimensions - } - - pub fn position(&self) -> Option<Delta<i32>> { - self.position - } - - pub fn command(&self) -> Option<&Shell<'_>> { - self.command.as_ref() - } - - pub fn config_path(&self) -> Option<Cow<'_, Path>> { - self.config.as_ref().map(|p| Cow::Borrowed(p.as_path())) - } -} diff --git a/src/config/bindings.rs b/src/config/bindings.rs deleted file mode 100644 index 7e69182b..00000000 --- a/src/config/bindings.rs +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use glutin::{ModifiersState, MouseButton}; - -use super::Key; -use crate::input::{Action, KeyBinding, MouseBinding}; -use crate::term::TermMode; - -macro_rules! bindings { - ( - $ty:ident; - $( - $key:path - $(,[$($mod:ident: $enabled:expr),*])* - $(,+$mode:expr)* - $(,~$notmode:expr)* - ;$action:expr - );* - $(;)* - ) => {{ - let mut v = Vec::new(); - - $( - let mut _mods = ModifiersState { - $($($mod: $enabled),*,)* - ..Default::default() - }; - let mut _mode = TermMode::empty(); - $(_mode = $mode;)* - let mut _notmode = TermMode::empty(); - $(_notmode = $notmode;)* - - v.push($ty { - trigger: $key, - mods: _mods, - mode: _mode, - notmode: _notmode, - action: $action, - }); - )* - - v - }} -} - -pub fn default_mouse_bindings() -> Vec<MouseBinding> { - bindings!( - MouseBinding; - MouseButton::Middle; Action::PasteSelection; - ) -} - -pub fn default_key_bindings() -> Vec<KeyBinding> { - let mut bindings = bindings!( - KeyBinding; - Key::Paste; Action::Paste; - Key::Copy; Action::Copy; - Key::L, [ctrl: true]; Action::ClearLogNotice; - Key::L, [ctrl: true]; Action::Esc("\x0c".into()); - Key::Home, [alt: true]; Action::Esc("\x1b[1;3H".into()); - Key::Home, +TermMode::APP_CURSOR; Action::Esc("\x1bOH".into()); - Key::Home, ~TermMode::APP_CURSOR; Action::Esc("\x1b[H".into()); - Key::End, [alt: true]; Action::Esc("\x1b[1;3F".into()); - Key::End, +TermMode::APP_CURSOR; Action::Esc("\x1bOF".into()); - Key::End, ~TermMode::APP_CURSOR; Action::Esc("\x1b[F".into()); - Key::PageUp, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollPageUp; - Key::PageUp, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[5;2~".into()); - Key::PageUp, [ctrl: true]; Action::Esc("\x1b[5;5~".into()); - Key::PageUp, [alt: true]; Action::Esc("\x1b[5;3~".into()); - Key::PageUp; Action::Esc("\x1b[5~".into()); - Key::PageDown, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollPageDown; - Key::PageDown, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[6;2~".into()); - Key::PageDown, [ctrl: true]; Action::Esc("\x1b[6;5~".into()); - Key::PageDown, [alt: true]; Action::Esc("\x1b[6;3~".into()); - Key::PageDown; Action::Esc("\x1b[6~".into()); - Key::Tab, [shift: true]; Action::Esc("\x1b[Z".into()); - Key::Back; Action::Esc("\x7f".into()); - Key::Back, [alt: true]; Action::Esc("\x1b\x7f".into()); - Key::Insert; Action::Esc("\x1b[2~".into()); - Key::Delete; Action::Esc("\x1b[3~".into()); - Key::Left, [shift: true]; Action::Esc("\x1b[1;2D".into()); - Key::Left, [ctrl: true]; Action::Esc("\x1b[1;5D".into()); - Key::Left, [alt: true]; Action::Esc("\x1b[1;3D".into()); - Key::Left, ~TermMode::APP_CURSOR; Action::Esc("\x1b[D".into()); - Key::Left, +TermMode::APP_CURSOR; Action::Esc("\x1bOD".into()); - Key::Right, [shift: true]; Action::Esc("\x1b[1;2C".into()); - Key::Right, [ctrl: true]; Action::Esc("\x1b[1;5C".into()); - Key::Right, [alt: true]; Action::Esc("\x1b[1;3C".into()); - Key::Right, ~TermMode::APP_CURSOR; Action::Esc("\x1b[C".into()); - Key::Right, +TermMode::APP_CURSOR; Action::Esc("\x1bOC".into()); - Key::Up, [shift: true]; Action::Esc("\x1b[1;2A".into()); - Key::Up, [ctrl: true]; Action::Esc("\x1b[1;5A".into()); - Key::Up, [alt: true]; Action::Esc("\x1b[1;3A".into()); - Key::Up, ~TermMode::APP_CURSOR; Action::Esc("\x1b[A".into()); - Key::Up, +TermMode::APP_CURSOR; Action::Esc("\x1bOA".into()); - Key::Down, [shift: true]; Action::Esc("\x1b[1;2B".into()); - Key::Down, [ctrl: true]; Action::Esc("\x1b[1;5B".into()); - Key::Down, [alt: true]; Action::Esc("\x1b[1;3B".into()); - Key::Down, ~TermMode::APP_CURSOR; Action::Esc("\x1b[B".into()); - Key::Down, +TermMode::APP_CURSOR; Action::Esc("\x1bOB".into()); - Key::F1; Action::Esc("\x1bOP".into()); - Key::F2; Action::Esc("\x1bOQ".into()); - Key::F3; Action::Esc("\x1bOR".into()); - Key::F4; Action::Esc("\x1bOS".into()); - Key::F5; Action::Esc("\x1b[15~".into()); - Key::F6; Action::Esc("\x1b[17~".into()); - Key::F7; Action::Esc("\x1b[18~".into()); - Key::F8; Action::Esc("\x1b[19~".into()); - Key::F9; Action::Esc("\x1b[20~".into()); - Key::F10; Action::Esc("\x1b[21~".into()); - Key::F11; Action::Esc("\x1b[23~".into()); - Key::F12; Action::Esc("\x1b[24~".into()); - Key::F1, [shift: true]; Action::Esc("\x1b[1;2P".into()); - Key::F2, [shift: true]; Action::Esc("\x1b[1;2Q".into()); - Key::F3, [shift: true]; Action::Esc("\x1b[1;2R".into()); - Key::F4, [shift: true]; Action::Esc("\x1b[1;2S".into()); - Key::F5, [shift: true]; Action::Esc("\x1b[15;2~".into()); - Key::F6, [shift: true]; Action::Esc("\x1b[17;2~".into()); - Key::F7, [shift: true]; Action::Esc("\x1b[18;2~".into()); - Key::F8, [shift: true]; Action::Esc("\x1b[19;2~".into()); - Key::F9, [shift: true]; Action::Esc("\x1b[20;2~".into()); - Key::F10, [shift: true]; Action::Esc("\x1b[21;2~".into()); - Key::F11, [shift: true]; Action::Esc("\x1b[23;2~".into()); - Key::F12, [shift: true]; Action::Esc("\x1b[24;2~".into()); - Key::F1, [ctrl: true]; Action::Esc("\x1b[1;5P".into()); - Key::F2, [ctrl: true]; Action::Esc("\x1b[1;5Q".into()); - Key::F3, [ctrl: true]; Action::Esc("\x1b[1;5R".into()); - Key::F4, [ctrl: true]; Action::Esc("\x1b[1;5S".into()); - Key::F5, [ctrl: true]; Action::Esc("\x1b[15;5~".into()); - Key::F6, [ctrl: true]; Action::Esc("\x1b[17;5~".into()); - Key::F7, [ctrl: true]; Action::Esc("\x1b[18;5~".into()); - Key::F8, [ctrl: true]; Action::Esc("\x1b[19;5~".into()); - Key::F9, [ctrl: true]; Action::Esc("\x1b[20;5~".into()); - Key::F10, [ctrl: true]; Action::Esc("\x1b[21;5~".into()); - Key::F11, [ctrl: true]; Action::Esc("\x1b[23;5~".into()); - Key::F12, [ctrl: true]; Action::Esc("\x1b[24;5~".into()); - Key::F1, [alt: true]; Action::Esc("\x1b[1;6P".into()); - Key::F2, [alt: true]; Action::Esc("\x1b[1;6Q".into()); - Key::F3, [alt: true]; Action::Esc("\x1b[1;6R".into()); - Key::F4, [alt: true]; Action::Esc("\x1b[1;6S".into()); - Key::F5, [alt: true]; Action::Esc("\x1b[15;6~".into()); - Key::F6, [alt: true]; Action::Esc("\x1b[17;6~".into()); - Key::F7, [alt: true]; Action::Esc("\x1b[18;6~".into()); - Key::F8, [alt: true]; Action::Esc("\x1b[19;6~".into()); - Key::F9, [alt: true]; Action::Esc("\x1b[20;6~".into()); - Key::F10, [alt: true]; Action::Esc("\x1b[21;6~".into()); - Key::F11, [alt: true]; Action::Esc("\x1b[23;6~".into()); - Key::F12, [alt: true]; Action::Esc("\x1b[24;6~".into()); - Key::F1, [logo: true]; Action::Esc("\x1b[1;3P".into()); - Key::F2, [logo: true]; Action::Esc("\x1b[1;3Q".into()); - Key::F3, [logo: true]; Action::Esc("\x1b[1;3R".into()); - Key::F4, [logo: true]; Action::Esc("\x1b[1;3S".into()); - Key::F5, [logo: true]; Action::Esc("\x1b[15;3~".into()); - Key::F6, [logo: true]; Action::Esc("\x1b[17;3~".into()); - Key::F7, [logo: true]; Action::Esc("\x1b[18;3~".into()); - Key::F8, [logo: true]; Action::Esc("\x1b[19;3~".into()); - Key::F9, [logo: true]; Action::Esc("\x1b[20;3~".into()); - Key::F10, [logo: true]; Action::Esc("\x1b[21;3~".into()); - Key::F11, [logo: true]; Action::Esc("\x1b[23;3~".into()); - Key::F12, [logo: true]; Action::Esc("\x1b[24;3~".into()); - Key::NumpadEnter; Action::Esc("\n".into()); - ); - - bindings.extend(platform_key_bindings()); - - bindings -} - -#[cfg(not(any(target_os = "macos", test)))] -fn common_keybindings() -> Vec<KeyBinding> { - bindings!( - KeyBinding; - Key::V, [ctrl: true, shift: true]; Action::Paste; - Key::C, [ctrl: true, shift: true]; Action::Copy; - Key::Insert, [shift: true]; Action::PasteSelection; - Key::Key0, [ctrl: true]; Action::ResetFontSize; - Key::Equals, [ctrl: true]; Action::IncreaseFontSize; - Key::Add, [ctrl: true]; Action::IncreaseFontSize; - Key::Subtract, [ctrl: true]; Action::DecreaseFontSize; - Key::Minus, [ctrl: true]; Action::DecreaseFontSize; - ) -} - -#[cfg(not(any(target_os = "macos", target_os = "windows", test)))] -pub fn platform_key_bindings() -> Vec<KeyBinding> { - common_keybindings() -} - -#[cfg(all(target_os = "windows", not(test)))] -pub fn platform_key_bindings() -> Vec<KeyBinding> { - let mut bindings = bindings!( - KeyBinding; - Key::Return, [alt: true]; Action::ToggleFullscreen; - ); - bindings.extend(common_keybindings()); - bindings -} - -#[cfg(all(target_os = "macos", not(test)))] -pub fn platform_key_bindings() -> Vec<KeyBinding> { - bindings!( - KeyBinding; - Key::Key0, [logo: true]; Action::ResetFontSize; - Key::Equals, [logo: true]; Action::IncreaseFontSize; - Key::Add, [logo: true]; Action::IncreaseFontSize; - Key::Minus, [logo: true]; Action::DecreaseFontSize; - Key::F, [ctrl: true, logo: true]; Action::ToggleFullscreen; - Key::K, [logo: true]; Action::ClearHistory; - Key::K, [logo: true]; Action::Esc("\x0c".into()); - Key::V, [logo: true]; Action::Paste; - Key::C, [logo: true]; Action::Copy; - Key::H, [logo: true]; Action::Hide; - Key::Q, [logo: true]; Action::Quit; - Key::W, [logo: true]; Action::Quit; - ) -} - -// Don't return any bindings for tests since they are commented-out by default -#[cfg(test)] -pub fn platform_key_bindings() -> Vec<KeyBinding> { - vec![] -} diff --git a/src/config/mod.rs b/src/config/mod.rs deleted file mode 100644 index 4d6026ca..00000000 --- a/src/config/mod.rs +++ /dev/null @@ -1,2749 +0,0 @@ -//! Configuration definitions and file loading -//! -//! Alacritty reads from a config file at startup to determine various runtime -//! parameters including font family and style, font size, etc. In the future, -//! the config file will also hold user and platform specific keybindings. -use std::borrow::Cow; -use std::collections::HashMap; -use std::fs::File; -use std::io::{self, Read, Write}; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::sync::mpsc; -use std::time::Duration; -use std::{env, fmt}; - -use font::Size; -use glutin::ModifiersState; -use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; -use serde::de::Error as SerdeError; -use serde::de::{MapAccess, Unexpected, Visitor}; -use serde::{self, de, Deserialize}; -use serde_yaml; - -use crate::ansi::CursorStyle; -use crate::cli::Options; -use crate::index::{Column, Line}; -use crate::input::{Action, Binding, KeyBinding, MouseBinding}; -use crate::term::color::Rgb; - -mod bindings; - -pub const SOURCE_FILE_PATH: &str = file!(); -const MAX_SCROLLBACK_LINES: u32 = 100_000; -static DEFAULT_ALACRITTY_CONFIG: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml")); - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Selection { - #[serde(deserialize_with = "deserialize_escape_chars")] - pub semantic_escape_chars: String, - #[serde(deserialize_with = "failure_default")] - pub save_to_clipboard: bool, -} - -impl Default for Selection { - fn default() -> Selection { - Selection { - semantic_escape_chars: default_escape_chars(), - save_to_clipboard: Default::default(), - } - } -} - -fn deserialize_escape_chars<'a, D>(deserializer: D) -> ::std::result::Result<String, D::Error> -where - D: de::Deserializer<'a>, -{ - match String::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_escape_chars()) - }, - } -} - -fn default_escape_chars() -> String { - String::from(",│`|:\"' ()[]{}<>") -} - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct ClickHandler { - #[serde(deserialize_with = "deserialize_duration_ms")] - pub threshold: Duration, -} - -impl Default for ClickHandler { - fn default() -> Self { - ClickHandler { threshold: default_threshold_ms() } - } -} - -fn default_threshold_ms() -> Duration { - Duration::from_millis(300) -} - -fn deserialize_duration_ms<'a, D>(deserializer: D) -> ::std::result::Result<Duration, D::Error> -where - D: de::Deserializer<'a>, -{ - match u64::deserialize(deserializer) { - Ok(threshold_ms) => Ok(Duration::from_millis(threshold_ms)), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_threshold_ms()) - }, - } -} - -#[serde(default)] -#[derive(Default, Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Mouse { - #[serde(deserialize_with = "failure_default")] - pub double_click: ClickHandler, - #[serde(deserialize_with = "failure_default")] - pub triple_click: ClickHandler, - #[serde(deserialize_with = "failure_default")] - pub hide_when_typing: bool, - #[serde(deserialize_with = "failure_default")] - pub url: Url, - - // TODO: DEPRECATED - pub faux_scrollback_lines: Option<usize>, -} - -#[serde(default)] -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Url { - // Program for opening links - #[serde(deserialize_with = "deserialize_launcher")] - pub launcher: Option<CommandWrapper>, - - // Modifier used to open links - #[serde(deserialize_with = "deserialize_modifiers")] - pub modifiers: ModifiersState, -} - -fn deserialize_launcher<'a, D>( - deserializer: D, -) -> ::std::result::Result<Option<CommandWrapper>, D::Error> -where - D: de::Deserializer<'a>, -{ - let default = Url::default().launcher; - - // Deserialize to generic value - let val = match serde_yaml::Value::deserialize(deserializer) { - Ok(val) => val, - Err(err) => { - error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); - return Ok(default); - }, - }; - - // Accept `None` to disable the launcher - if val.as_str().filter(|v| v.to_lowercase() == "none").is_some() { - return Ok(None); - } - - match <Option<CommandWrapper>>::deserialize(val) { - Ok(launcher) => Ok(launcher), - Err(err) => { - error!("Problem with config: {}; using {}", err, default.clone().unwrap().program()); - Ok(default) - }, - } -} - -impl Default for Url { - fn default() -> Url { - Url { - #[cfg(not(any(target_os = "macos", windows)))] - launcher: Some(CommandWrapper::Just(String::from("xdg-open"))), - #[cfg(target_os = "macos")] - launcher: Some(CommandWrapper::Just(String::from("open"))), - #[cfg(windows)] - launcher: Some(CommandWrapper::Just(String::from("explorer"))), - modifiers: Default::default(), - } - } -} - -fn deserialize_modifiers<'a, D>(deserializer: D) -> ::std::result::Result<ModifiersState, D::Error> -where - D: de::Deserializer<'a>, -{ - ModsWrapper::deserialize(deserializer).map(ModsWrapper::into_inner) -} - -/// `VisualBellAnimations` are modeled after a subset of CSS transitions and Robert -/// Penner's Easing Functions. -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)] -pub enum VisualBellAnimation { - Ease, // CSS - EaseOut, // CSS - EaseOutSine, // Penner - EaseOutQuad, // Penner - EaseOutCubic, // Penner - EaseOutQuart, // Penner - EaseOutQuint, // Penner - EaseOutExpo, // Penner - EaseOutCirc, // Penner - Linear, -} - -impl Default for VisualBellAnimation { - fn default() -> Self { - VisualBellAnimation::EaseOutExpo - } -} - -#[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct VisualBellConfig { - /// Visual bell animation function - #[serde(deserialize_with = "failure_default")] - animation: VisualBellAnimation, - - /// Visual bell duration in milliseconds - #[serde(deserialize_with = "failure_default")] - duration: u16, - - /// Visual bell flash color - #[serde(deserialize_with = "rgb_from_hex")] - color: Rgb, -} - -impl Default for VisualBellConfig { - fn default() -> VisualBellConfig { - VisualBellConfig { - animation: Default::default(), - duration: Default::default(), - color: default_visual_bell_color(), - } - } -} - -fn default_visual_bell_color() -> Rgb { - Rgb { r: 255, g: 255, b: 255 } -} - -impl VisualBellConfig { - /// Visual bell animation - #[inline] - pub fn animation(&self) -> VisualBellAnimation { - self.animation - } - - /// Visual bell duration in milliseconds - #[inline] - pub fn duration(&self) -> Duration { - Duration::from_millis(u64::from(self.duration)) - } - - /// Visual bell flash color - #[inline] - pub fn color(&self) -> Rgb { - self.color - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct Shell<'a> { - program: Cow<'a, str>, - - #[serde(default, deserialize_with = "failure_default")] - args: Vec<String>, -} - -impl<'a> Shell<'a> { - pub fn new<S>(program: S) -> Shell<'a> - where - S: Into<Cow<'a, str>>, - { - Shell { program: program.into(), args: Vec::new() } - } - - pub fn new_with_args<S>(program: S, args: Vec<String>) -> Shell<'a> - where - S: Into<Cow<'a, str>>, - { - Shell { program: program.into(), args } - } - - pub fn program(&self) -> &str { - &*self.program - } - - pub fn args(&self) -> &[String] { - self.args.as_slice() - } -} - -/// Wrapper around f32 that represents an alpha value between 0.0 and 1.0 -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Alpha(f32); - -impl Alpha { - pub fn new(value: f32) -> Self { - Alpha(Self::clamp_to_valid_range(value)) - } - - pub fn set(&mut self, value: f32) { - self.0 = Self::clamp_to_valid_range(value); - } - - #[inline] - pub fn get(self) -> f32 { - self.0 - } - - fn clamp_to_valid_range(value: f32) -> f32 { - if value < 0.0 { - 0.0 - } else if value > 1.0 { - 1.0 - } else { - value - } - } -} - -impl Default for Alpha { - fn default() -> Self { - Alpha(1.0) - } -} - -#[derive(Debug, Deserialize, Copy, Clone, PartialEq, Eq)] -pub enum StartupMode { - Windowed, - Maximized, - Fullscreen, - #[cfg(target_os = "macos")] - SimpleFullscreen, -} - -impl Default for StartupMode { - fn default() -> StartupMode { - StartupMode::Windowed - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Decorations { - Full, - Transparent, - Buttonless, - None, -} - -impl Default for Decorations { - fn default() -> Decorations { - Decorations::Full - } -} - -impl<'de> Deserialize<'de> for Decorations { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Decorations, D::Error> - where - D: de::Deserializer<'de>, - { - struct DecorationsVisitor; - - impl<'de> Visitor<'de> for DecorationsVisitor { - type Value = Decorations; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Some subset of full|transparent|buttonless|none") - } - - #[cfg(target_os = "macos")] - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E> - where - E: de::Error, - { - match value.to_lowercase().as_str() { - "transparent" => Ok(Decorations::Transparent), - "buttonless" => Ok(Decorations::Buttonless), - "none" => Ok(Decorations::None), - "full" => Ok(Decorations::Full), - "true" => { - error!( - "Deprecated decorations boolean value, use one of \ - transparent|buttonless|none|full instead; falling back to \"full\"" - ); - Ok(Decorations::Full) - }, - "false" => { - error!( - "Deprecated decorations boolean value, use one of \ - transparent|buttonless|none|full instead; falling back to \"none\"" - ); - Ok(Decorations::None) - }, - _ => { - error!("Invalid decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - } - } - - #[cfg(not(target_os = "macos"))] - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Decorations, E> - where - E: de::Error, - { - match value.to_lowercase().as_str() { - "none" => Ok(Decorations::None), - "full" => Ok(Decorations::Full), - "true" => { - error!( - "Deprecated decorations boolean value, use one of none|full instead; \ - falling back to \"full\"" - ); - Ok(Decorations::Full) - }, - "false" => { - error!( - "Deprecated decorations boolean value, use one of none|full instead; \ - falling back to \"none\"" - ); - Ok(Decorations::None) - }, - "transparent" | "buttonless" => { - error!("macOS-only decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - _ => { - error!("Invalid decorations value: {}; using default value", value); - Ok(Decorations::Full) - }, - } - } - } - - deserializer.deserialize_str(DecorationsVisitor) - } -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Deserialize, PartialEq, Eq)] -pub struct WindowConfig { - /// Initial dimensions - #[serde(default, deserialize_with = "failure_default")] - dimensions: Dimensions, - - /// Initial position - #[serde(default, deserialize_with = "failure_default")] - position: Option<Delta<i32>>, - - /// Pixel padding - #[serde(deserialize_with = "deserialize_padding")] - padding: Delta<u8>, - - /// Draw the window with title bar / borders - #[serde(deserialize_with = "failure_default")] - decorations: Decorations, - - /// Spread out additional padding evenly - #[serde(deserialize_with = "failure_default")] - dynamic_padding: bool, - - /// Startup mode - #[serde(deserialize_with = "failure_default")] - startup_mode: StartupMode, - - /// TODO: DEPRECATED - #[serde(deserialize_with = "failure_default")] - start_maximized: Option<bool>, -} - -impl Default for WindowConfig { - fn default() -> Self { - WindowConfig { - dimensions: Default::default(), - position: Default::default(), - padding: default_padding(), - decorations: Default::default(), - dynamic_padding: Default::default(), - start_maximized: Default::default(), - startup_mode: Default::default(), - } - } -} - -fn default_padding() -> Delta<u8> { - Delta { x: 2, y: 2 } -} - -fn deserialize_padding<'a, D>(deserializer: D) -> ::std::result::Result<Delta<u8>, D::Error> -where - D: de::Deserializer<'a>, -{ - match Delta::deserialize(deserializer) { - Ok(delta) => Ok(delta), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_padding()) - }, - } -} - -impl WindowConfig { - pub fn decorations(&self) -> Decorations { - self.decorations - } - - pub fn dynamic_padding(&self) -> bool { - self.dynamic_padding - } - - pub fn startup_mode(&self) -> StartupMode { - self.startup_mode - } - - pub fn position(&self) -> Option<Delta<i32>> { - self.position - } -} - -/// Top-level config type -#[derive(Debug, PartialEq, Deserialize)] -pub struct Config { - /// Pixel padding - #[serde(default, deserialize_with = "failure_default")] - padding: Option<Delta<u8>>, - - /// TERM env variable - #[serde(default, deserialize_with = "failure_default")] - env: HashMap<String, String>, - - /// Font configuration - #[serde(default, deserialize_with = "failure_default")] - font: Font, - - /// Should show render timer - #[serde(default, deserialize_with = "failure_default")] - render_timer: bool, - - /// Should draw bold text with brighter colors instead of bold font - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - draw_bold_text_with_bright_colors: bool, - - #[serde(default, deserialize_with = "failure_default")] - colors: Colors, - - /// Background opacity from 0.0 to 1.0 - #[serde(default, deserialize_with = "failure_default")] - background_opacity: Alpha, - - /// Window configuration - #[serde(default, deserialize_with = "failure_default")] - window: WindowConfig, - - /// Keybindings - #[serde(default = "default_key_bindings", deserialize_with = "deserialize_key_bindings")] - key_bindings: Vec<KeyBinding>, - - /// Bindings for the mouse - #[serde(default = "default_mouse_bindings", deserialize_with = "deserialize_mouse_bindings")] - mouse_bindings: Vec<MouseBinding>, - - #[serde(default, deserialize_with = "failure_default")] - selection: Selection, - - #[serde(default, deserialize_with = "failure_default")] - mouse: Mouse, - - /// Path to a shell program to run on startup - #[serde(default, deserialize_with = "failure_default")] - shell: Option<Shell<'static>>, - - /// Path where config was loaded from - #[serde(default, deserialize_with = "failure_default")] - config_path: Option<PathBuf>, - - /// Visual bell configuration - #[serde(default, deserialize_with = "failure_default")] - visual_bell: VisualBellConfig, - - /// Use dynamic title - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - dynamic_title: bool, - - /// Live config reload - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - live_config_reload: bool, - - /// Number of spaces in one tab - #[serde(default = "default_tabspaces", deserialize_with = "deserialize_tabspaces")] - tabspaces: usize, - - /// How much scrolling history to keep - #[serde(default, deserialize_with = "failure_default")] - scrolling: Scrolling, - - /// Cursor configuration - #[serde(default, deserialize_with = "failure_default")] - cursor: Cursor, - - /// Keep the log file after quitting - #[serde(default, deserialize_with = "failure_default")] - persistent_logging: bool, - - /// Enable experimental conpty backend instead of using winpty. - /// Will only take effect on Windows 10 Oct 2018 and later. - #[cfg(windows)] - #[serde(default, deserialize_with = "failure_default")] - enable_experimental_conpty_backend: bool, - - /// Send escape sequences using the alt key. - #[serde(default = "default_true_bool", deserialize_with = "deserialize_true_bool")] - alt_send_esc: bool, - - // TODO: DEPRECATED - custom_cursor_colors: Option<bool>, - - // TODO: DEPRECATED - hide_cursor_when_typing: Option<bool>, - - // TODO: DEPRECATED - cursor_style: Option<CursorStyle>, - - // TODO: DEPRECATED - unfocused_hollow_cursor: Option<bool>, - - // TODO: DEPRECATED - dimensions: Option<Dimensions>, -} - -impl Default for Config { - fn default() -> Self { - serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("default config is invalid") - } -} - -fn default_key_bindings() -> Vec<KeyBinding> { - bindings::default_key_bindings() -} - -fn default_mouse_bindings() -> Vec<MouseBinding> { - bindings::default_mouse_bindings() -} - -fn deserialize_key_bindings<'a, D>( - deserializer: D, -) -> ::std::result::Result<Vec<KeyBinding>, D::Error> -where - D: de::Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_key_bindings()) -} - -fn deserialize_mouse_bindings<'a, D>( - deserializer: D, -) -> ::std::result::Result<Vec<MouseBinding>, D::Error> -where - D: de::Deserializer<'a>, -{ - deserialize_bindings(deserializer, bindings::default_mouse_bindings()) -} - -fn deserialize_bindings<'a, D, T>( - deserializer: D, - mut default: Vec<Binding<T>>, -) -> ::std::result::Result<Vec<Binding<T>>, D::Error> -where - D: de::Deserializer<'a>, - T: Copy + Eq + std::hash::Hash + std::fmt::Debug, - Binding<T>: de::Deserialize<'a>, -{ - let mut bindings: Vec<Binding<T>> = failure_default_vec(deserializer)?; - - for binding in bindings.iter() { - default.retain(|b| !b.triggers_match(binding)); - } - - bindings.extend(default); - - Ok(bindings) -} - -fn failure_default_vec<'a, D, T>(deserializer: D) -> ::std::result::Result<Vec<T>, D::Error> -where - D: de::Deserializer<'a>, - T: Deserialize<'a>, -{ - // Deserialize as generic vector - let vec = match Vec::<serde_yaml::Value>::deserialize(deserializer) { - Ok(vec) => vec, - Err(err) => { - error!("Problem with config: {}; using empty vector", err); - return Ok(Vec::new()); - }, - }; - - // Move to lossy vector - let mut bindings: Vec<T> = Vec::new(); - for value in vec { - match T::deserialize(value) { - Ok(binding) => bindings.push(binding), - Err(err) => { - error!("Problem with config: {}; skipping value", err); - }, - } - } - - Ok(bindings) -} - -fn default_tabspaces() -> usize { - 8 -} - -fn deserialize_tabspaces<'a, D>(deserializer: D) -> ::std::result::Result<usize, D::Error> -where - D: de::Deserializer<'a>, -{ - match usize::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using 8", err); - Ok(default_tabspaces()) - }, - } -} - -fn deserialize_true_bool<'a, D>(deserializer: D) -> ::std::result::Result<bool, D::Error> -where - D: de::Deserializer<'a>, -{ - match bool::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using true", err); - Ok(true) - }, - } -} - -fn default_true_bool() -> bool { - true -} - -fn failure_default<'a, D, T>(deserializer: D) -> ::std::result::Result<T, D::Error> -where - D: de::Deserializer<'a>, - T: Deserialize<'a> + Default, -{ - match T::deserialize(deserializer) { - Ok(value) => Ok(value), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(T::default()) - }, - } -} - -/// Struct for scrolling related settings -#[serde(default)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] -pub struct Scrolling { - #[serde(deserialize_with = "deserialize_scrolling_history")] - pub history: u32, - #[serde(deserialize_with = "deserialize_scrolling_multiplier")] - pub multiplier: u8, - #[serde(deserialize_with = "deserialize_scrolling_multiplier")] - pub faux_multiplier: u8, - #[serde(deserialize_with = "failure_default")] - pub auto_scroll: bool, -} - -impl Default for Scrolling { - fn default() -> Self { - Self { - history: default_scrolling_history(), - multiplier: default_scrolling_multiplier(), - faux_multiplier: default_scrolling_multiplier(), - auto_scroll: Default::default(), - } - } -} - -fn default_scrolling_history() -> u32 { - 10_000 -} - -// Default for normal and faux scrolling -fn default_scrolling_multiplier() -> u8 { - 3 -} - -fn deserialize_scrolling_history<'a, D>(deserializer: D) -> ::std::result::Result<u32, D::Error> -where - D: de::Deserializer<'a>, -{ - match u32::deserialize(deserializer) { - Ok(lines) => { - if lines > MAX_SCROLLBACK_LINES { - error!( - "Problem with config: scrollback size is {}, but expected a maximum of {}; \ - using {1} instead", - lines, MAX_SCROLLBACK_LINES, - ); - Ok(MAX_SCROLLBACK_LINES) - } else { - Ok(lines) - } - }, - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_scrolling_history()) - }, - } -} - -fn deserialize_scrolling_multiplier<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error> -where - D: de::Deserializer<'a>, -{ - match u8::deserialize(deserializer) { - Ok(lines) => Ok(lines), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_scrolling_multiplier()) - }, - } -} - -/// Newtype for implementing deserialize on glutin Mods -/// -/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the -/// impl below. -#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)] -struct ModsWrapper(ModifiersState); - -impl ModsWrapper { - fn into_inner(self) -> ModifiersState { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for ModsWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct ModsVisitor; - - impl<'a> Visitor<'a> for ModsVisitor { - type Value = ModsWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModsWrapper, E> - where - E: de::Error, - { - let mut res = ModifiersState::default(); - for modifier in value.split('|') { - match modifier.trim() { - "Command" | "Super" => res.logo = true, - "Shift" => res.shift = true, - "Alt" | "Option" => res.alt = true, - "Control" => res.ctrl = true, - "None" => (), - _ => error!("Unknown modifier {:?}", modifier), - } - } - - Ok(ModsWrapper(res)) - } - } - - deserializer.deserialize_str(ModsVisitor) - } -} - -struct ActionWrapper(crate::input::Action); - -impl ActionWrapper { - fn into_inner(self) -> crate::input::Action { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for ActionWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct ActionVisitor; - - impl<'a> Visitor<'a> for ActionVisitor { - type Value = ActionWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str( - "Paste, Copy, PasteSelection, IncreaseFontSize, DecreaseFontSize, \ - ResetFontSize, ScrollPageUp, ScrollPageDown, ScrollLineUp, ScrollLineDown, \ - ScrollToTop, ScrollToBottom, ClearHistory, Hide, ClearLogNotice, \ - SpawnNewInstance, ToggleFullscreen, ToggleSimpleFullscreen, None or Quit", - ) - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<ActionWrapper, E> - where - E: de::Error, - { - Ok(ActionWrapper(match value { - "Paste" => Action::Paste, - "Copy" => Action::Copy, - "PasteSelection" => Action::PasteSelection, - "IncreaseFontSize" => Action::IncreaseFontSize, - "DecreaseFontSize" => Action::DecreaseFontSize, - "ResetFontSize" => Action::ResetFontSize, - "ScrollPageUp" => Action::ScrollPageUp, - "ScrollPageDown" => Action::ScrollPageDown, - "ScrollLineUp" => Action::ScrollLineUp, - "ScrollLineDown" => Action::ScrollLineDown, - "ScrollToTop" => Action::ScrollToTop, - "ScrollToBottom" => Action::ScrollToBottom, - "ClearHistory" => Action::ClearHistory, - "Hide" => Action::Hide, - "Quit" => Action::Quit, - "ClearLogNotice" => Action::ClearLogNotice, - "SpawnNewInstance" => Action::SpawnNewInstance, - "ToggleFullscreen" => Action::ToggleFullscreen, - #[cfg(target_os = "macos")] - "ToggleSimpleFullscreen" => Action::ToggleSimpleFullscreen, - "None" => Action::None, - _ => return Err(E::invalid_value(Unexpected::Str(value), &self)), - })) - } - } - deserializer.deserialize_str(ActionVisitor) - } -} - -#[serde(untagged)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub enum CommandWrapper { - Just(String), - WithArgs { - program: String, - #[serde(default)] - args: Vec<String>, - }, -} - -impl CommandWrapper { - pub fn program(&self) -> &str { - match self { - CommandWrapper::Just(program) => program, - CommandWrapper::WithArgs { program, .. } => program, - } - } - - pub fn args(&self) -> &[String] { - match self { - CommandWrapper::Just(_) => &[], - CommandWrapper::WithArgs { args, .. } => args, - } - } -} - -use crate::term::{mode, TermMode}; - -struct ModeWrapper { - pub mode: TermMode, - pub not_mode: TermMode, -} - -impl<'a> de::Deserialize<'a> for ModeWrapper { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct ModeVisitor; - - impl<'a> Visitor<'a> for ModeVisitor { - type Value = ModeWrapper; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Combination of AppCursor | AppKeypad, possibly with negation (~)") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E> - where - E: de::Error, - { - let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() }; - - for modifier in value.split('|') { - match modifier.trim() { - "AppCursor" => res.mode |= mode::TermMode::APP_CURSOR, - "~AppCursor" => res.not_mode |= mode::TermMode::APP_CURSOR, - "AppKeypad" => res.mode |= mode::TermMode::APP_KEYPAD, - "~AppKeypad" => res.not_mode |= mode::TermMode::APP_KEYPAD, - "~Alt" => res.not_mode |= mode::TermMode::ALT_SCREEN, - "Alt" => res.mode |= mode::TermMode::ALT_SCREEN, - _ => error!("Unknown mode {:?}", modifier), - } - } - - Ok(res) - } - } - deserializer.deserialize_str(ModeVisitor) - } -} - -struct MouseButton(::glutin::MouseButton); - -impl MouseButton { - fn into_inner(self) -> ::glutin::MouseButton { - self.0 - } -} - -impl<'a> de::Deserialize<'a> for MouseButton { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - struct MouseButtonVisitor; - - impl<'a> Visitor<'a> for MouseButtonVisitor { - type Value = MouseButton; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Left, Right, Middle, or a number") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButton, E> - where - E: de::Error, - { - match value { - "Left" => Ok(MouseButton(::glutin::MouseButton::Left)), - "Right" => Ok(MouseButton(::glutin::MouseButton::Right)), - "Middle" => Ok(MouseButton(::glutin::MouseButton::Middle)), - _ => { - if let Ok(index) = u8::from_str(value) { - Ok(MouseButton(::glutin::MouseButton::Other(index))) - } else { - Err(E::invalid_value(Unexpected::Str(value), &self)) - } - }, - } - } - } - - deserializer.deserialize_str(MouseButtonVisitor) - } -} - -/// Bindings are deserialized into a `RawBinding` before being parsed as a -/// `KeyBinding` or `MouseBinding`. -#[derive(PartialEq, Eq)] -struct RawBinding { - key: Option<Key>, - mouse: Option<::glutin::MouseButton>, - mods: ModifiersState, - mode: TermMode, - notmode: TermMode, - action: Action, -} - -impl RawBinding { - fn into_mouse_binding(self) -> ::std::result::Result<MouseBinding, Self> { - if let Some(mouse) = self.mouse { - Ok(Binding { - trigger: mouse, - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } - - fn into_key_binding(self) -> ::std::result::Result<KeyBinding, Self> { - if let Some(key) = self.key { - Ok(KeyBinding { - trigger: key, - mods: self.mods, - action: self.action, - mode: self.mode, - notmode: self.notmode, - }) - } else { - Err(self) - } - } -} - -impl<'a> de::Deserialize<'a> for RawBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - enum Field { - Key, - Mods, - Mode, - Action, - Chars, - Mouse, - Command, - } - - impl<'a> de::Deserialize<'a> for Field { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error> - where - D: de::Deserializer<'a>, - { - struct FieldVisitor; - - static FIELDS: &'static [&'static str] = - &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - - impl<'a> Visitor<'a> for FieldVisitor { - type Value = Field; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("binding fields") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Field, E> - where - E: de::Error, - { - match value { - "key" => Ok(Field::Key), - "mods" => Ok(Field::Mods), - "mode" => Ok(Field::Mode), - "action" => Ok(Field::Action), - "chars" => Ok(Field::Chars), - "mouse" => Ok(Field::Mouse), - "command" => Ok(Field::Command), - _ => Err(E::unknown_field(value, FIELDS)), - } - } - } - - deserializer.deserialize_str(FieldVisitor) - } - } - - struct RawBindingVisitor; - impl<'a> Visitor<'a> for RawBindingVisitor { - type Value = RawBinding; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("binding specification") - } - - fn visit_map<V>(self, mut map: V) -> ::std::result::Result<RawBinding, V::Error> - where - V: MapAccess<'a>, - { - let mut mods: Option<ModifiersState> = None; - let mut key: Option<Key> = None; - let mut chars: Option<String> = None; - let mut action: Option<crate::input::Action> = None; - let mut mode: Option<TermMode> = None; - let mut not_mode: Option<TermMode> = None; - let mut mouse: Option<::glutin::MouseButton> = None; - let mut command: Option<CommandWrapper> = None; - - use ::serde::de::Error; - - while let Some(struct_key) = map.next_key::<Field>()? { - match struct_key { - Field::Key => { - if key.is_some() { - return Err(<V::Error as Error>::duplicate_field("key")); - } - - let val = map.next_value::<serde_yaml::Value>()?; - if val.is_u64() { - let scancode = val.as_u64().unwrap(); - if scancode > u64::from(::std::u32::MAX) { - return Err(<V::Error as Error>::custom(format!( - "Invalid key binding, scancode too big: {}", - scancode - ))); - } - key = Some(Key::Scancode(scancode as u32)); - } else { - let k = Key::deserialize(val).map_err(V::Error::custom)?; - key = Some(k); - } - }, - Field::Mods => { - if mods.is_some() { - return Err(<V::Error as Error>::duplicate_field("mods")); - } - - mods = Some(map.next_value::<ModsWrapper>()?.into_inner()); - }, - Field::Mode => { - if mode.is_some() { - return Err(<V::Error as Error>::duplicate_field("mode")); - } - - let mode_deserializer = map.next_value::<ModeWrapper>()?; - mode = Some(mode_deserializer.mode); - not_mode = Some(mode_deserializer.not_mode); - }, - Field::Action => { - if action.is_some() { - return Err(<V::Error as Error>::duplicate_field("action")); - } - - action = Some(map.next_value::<ActionWrapper>()?.into_inner()); - }, - Field::Chars => { - if chars.is_some() { - return Err(<V::Error as Error>::duplicate_field("chars")); - } - - chars = Some(map.next_value()?); - }, - Field::Mouse => { - if chars.is_some() { - return Err(<V::Error as Error>::duplicate_field("mouse")); - } - - mouse = Some(map.next_value::<MouseButton>()?.into_inner()); - }, - Field::Command => { - if command.is_some() { - return Err(<V::Error as Error>::duplicate_field("command")); - } - - command = Some(map.next_value::<CommandWrapper>()?); - }, - } - } - - let action = match (action, chars, command) { - (Some(action), None, None) => action, - (None, Some(chars), None) => Action::Esc(chars), - (None, None, Some(cmd)) => match cmd { - CommandWrapper::Just(program) => Action::Command(program, vec![]), - CommandWrapper::WithArgs { program, args } => { - Action::Command(program, args) - }, - }, - (None, None, None) => { - return Err(V::Error::custom("must specify chars, action or command")); - }, - _ => { - return Err(V::Error::custom("must specify only chars, action or command")) - }, - }; - - let mode = mode.unwrap_or_else(TermMode::empty); - let not_mode = not_mode.unwrap_or_else(TermMode::empty); - let mods = mods.unwrap_or_else(ModifiersState::default); - - if mouse.is_none() && key.is_none() { - return Err(V::Error::custom("bindings require mouse button or key")); - } - - Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods }) - } - } - - const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - - deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) - } -} - -impl<'a> de::Deserialize<'a> for Alpha { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - let value = f32::deserialize(deserializer)?; - Ok(Alpha::new(value)) - } -} - -impl<'a> de::Deserialize<'a> for MouseBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - let raw = RawBinding::deserialize(deserializer)?; - raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding")) - } -} - -impl<'a> de::Deserialize<'a> for KeyBinding { - fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: de::Deserializer<'a>, - { - let raw = RawBinding::deserialize(deserializer)?; - raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding")) - } -} - -/// Errors occurring during config loading -#[derive(Debug)] -pub enum Error { - /// Config file not found - NotFound, - - /// Config file empty - Empty, - - /// Couldn't read $HOME environment variable - ReadingEnvHome(env::VarError), - - /// io error reading file - Io(io::Error), - - /// Not valid yaml or missing parameters - Yaml(serde_yaml::Error), -} - -#[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct Colors { - #[serde(deserialize_with = "failure_default")] - pub primary: PrimaryColors, - #[serde(deserialize_with = "failure_default")] - pub cursor: CursorColors, - #[serde(deserialize_with = "failure_default")] - pub selection: SelectionColors, - #[serde(deserialize_with = "deserialize_normal_colors")] - pub normal: AnsiColors, - #[serde(deserialize_with = "deserialize_bright_colors")] - pub bright: AnsiColors, - #[serde(deserialize_with = "failure_default")] - pub dim: Option<AnsiColors>, - #[serde(deserialize_with = "failure_default_vec")] - pub indexed_colors: Vec<IndexedColor>, -} - -impl Default for Colors { - fn default() -> Colors { - Colors { - primary: Default::default(), - cursor: Default::default(), - selection: Default::default(), - normal: default_normal_colors(), - bright: default_bright_colors(), - dim: Default::default(), - indexed_colors: Default::default(), - } - } -} - -fn default_normal_colors() -> AnsiColors { - AnsiColors { - black: Rgb { r: 0x00, g: 0x00, b: 0x00 }, - red: Rgb { r: 0xd5, g: 0x4e, b: 0x53 }, - green: Rgb { r: 0xb9, g: 0xca, b: 0x4a }, - yellow: Rgb { r: 0xe6, g: 0xc5, b: 0x47 }, - blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, - magenta: Rgb { r: 0xc3, g: 0x97, b: 0xd8 }, - cyan: Rgb { r: 0x70, g: 0xc0, b: 0xba }, - white: Rgb { r: 0xea, g: 0xea, b: 0xea }, - } -} - -fn default_bright_colors() -> AnsiColors { - AnsiColors { - black: Rgb { r: 0x66, g: 0x66, b: 0x66 }, - red: Rgb { r: 0xff, g: 0x33, b: 0x34 }, - green: Rgb { r: 0x9e, g: 0xc4, b: 0x00 }, - yellow: Rgb { r: 0xe7, g: 0xc5, b: 0x47 }, - blue: Rgb { r: 0x7a, g: 0xa6, b: 0xda }, - magenta: Rgb { r: 0xb7, g: 0x7e, b: 0xe0 }, - cyan: Rgb { r: 0x54, g: 0xce, b: 0xd6 }, - white: Rgb { r: 0xff, g: 0xff, b: 0xff }, - } -} - -fn deserialize_normal_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error> -where - D: de::Deserializer<'a>, -{ - match AnsiColors::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_normal_colors()) - }, - } -} - -fn deserialize_bright_colors<'a, D>(deserializer: D) -> ::std::result::Result<AnsiColors, D::Error> -where - D: de::Deserializer<'a>, -{ - match AnsiColors::deserialize(deserializer) { - Ok(escape_chars) => Ok(escape_chars), - Err(err) => { - error!("Problem with config: {}; using default value", err); - Ok(default_bright_colors()) - }, - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct IndexedColor { - #[serde(deserialize_with = "deserialize_color_index")] - pub index: u8, - #[serde(deserialize_with = "rgb_from_hex")] - pub color: Rgb, -} - -fn deserialize_color_index<'a, D>(deserializer: D) -> ::std::result::Result<u8, D::Error> -where - D: de::Deserializer<'a>, -{ - match u8::deserialize(deserializer) { - Ok(index) => { - if index < 16 { - error!( - "Problem with config: indexed_color's index is {}, but a value bigger than 15 \ - was expected; ignoring setting", - index - ); - - // Return value out of range to ignore this color - Ok(0) - } else { - Ok(index) - } - }, - Err(err) => { - error!("Problem with config: {}; ignoring setting", err); - - // Return value out of range to ignore this color - Ok(0) - }, - } -} - -#[serde(default)] -#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct Cursor { - #[serde(deserialize_with = "failure_default")] - pub style: CursorStyle, - #[serde(deserialize_with = "deserialize_true_bool")] - pub unfocused_hollow: bool, -} - -impl Default for Cursor { - fn default() -> Self { - Self { style: Default::default(), unfocused_hollow: true } - } -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)] -pub struct CursorColors { - #[serde(deserialize_with = "deserialize_optional_color")] - pub text: Option<Rgb>, - #[serde(deserialize_with = "deserialize_optional_color")] - pub cursor: Option<Rgb>, -} - -#[serde(default)] -#[derive(Debug, Copy, Clone, Default, Deserialize, PartialEq, Eq)] -pub struct SelectionColors { - #[serde(deserialize_with = "deserialize_optional_color")] - pub text: Option<Rgb>, - #[serde(deserialize_with = "deserialize_optional_color")] - pub background: Option<Rgb>, -} - -#[serde(default)] -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct PrimaryColors { - #[serde(deserialize_with = "rgb_from_hex")] - pub background: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub foreground: Rgb, - #[serde(deserialize_with = "deserialize_optional_color")] - pub bright_foreground: Option<Rgb>, - #[serde(deserialize_with = "deserialize_optional_color")] - pub dim_foreground: Option<Rgb>, -} - -impl Default for PrimaryColors { - fn default() -> Self { - PrimaryColors { - background: default_background(), - foreground: default_foreground(), - bright_foreground: Default::default(), - dim_foreground: Default::default(), - } - } -} - -fn deserialize_optional_color<'a, D>( - deserializer: D, -) -> ::std::result::Result<Option<Rgb>, D::Error> -where - D: de::Deserializer<'a>, -{ - match Option::deserialize(deserializer) { - Ok(Some(color)) => { - let color: serde_yaml::Value = color; - Ok(Some(rgb_from_hex(color).unwrap())) - }, - Ok(None) => Ok(None), - Err(err) => { - error!("Problem with config: {}; using standard foreground color", err); - Ok(None) - }, - } -} - -fn default_background() -> Rgb { - Rgb { r: 0, g: 0, b: 0 } -} - -fn default_foreground() -> Rgb { - Rgb { r: 0xea, g: 0xea, b: 0xea } -} - -/// The 8-colors sections of config -#[derive(Debug, Deserialize, PartialEq, Eq)] -pub struct AnsiColors { - #[serde(deserialize_with = "rgb_from_hex")] - pub black: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub red: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub green: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub yellow: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub blue: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub magenta: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub cyan: Rgb, - #[serde(deserialize_with = "rgb_from_hex")] - pub white: Rgb, -} - -/// Deserialize an Rgb from a hex string -/// -/// This is *not* the deserialize impl for Rgb since we want a symmetric -/// serialize/deserialize impl for ref tests. -fn rgb_from_hex<'a, D>(deserializer: D) -> ::std::result::Result<Rgb, D::Error> -where - D: de::Deserializer<'a>, -{ - struct RgbVisitor; - - impl<'a> Visitor<'a> for RgbVisitor { - type Value = Rgb; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("hex color like 0xff00ff") - } - - fn visit_str<E>(self, value: &str) -> ::std::result::Result<Rgb, E> - where - E: ::serde::de::Error, - { - Rgb::from_str(&value[..]) - .map_err(|_| E::custom("failed to parse rgb; expected hex color like 0xff00ff")) - } - } - - let rgb = deserializer.deserialize_str(RgbVisitor); - - // Use #ff00ff as fallback color - match rgb { - Ok(rgb) => Ok(rgb), - Err(err) => { - error!("Problem with config: {}; using color #ff00ff", err); - Ok(Rgb { r: 255, g: 0, b: 255 }) - }, - } -} - -impl FromStr for Rgb { - type Err = (); - - fn from_str(s: &str) -> ::std::result::Result<Rgb, ()> { - let mut chars = s.chars(); - let mut rgb = Rgb::default(); - - macro_rules! component { - ($($c:ident),*) => { - $( - match chars.next().and_then(|c| c.to_digit(16)) { - Some(val) => rgb.$c = (val as u8) << 4, - None => return Err(()) - } - - match chars.next().and_then(|c| c.to_digit(16)) { - Some(val) => rgb.$c |= val as u8, - None => return Err(()) - } - )* - } - } - - match chars.next() { - Some('0') => { - if chars.next() != Some('x') { - return Err(()); - } - }, - Some('#') => (), - _ => return Err(()), - } - - component!(r, g, b); - - Ok(rgb) - } -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - Error::NotFound | Error::Empty => None, - Error::ReadingEnvHome(ref err) => Some(err), - Error::Io(ref err) => Some(err), - Error::Yaml(ref err) => Some(err), - } - } - - fn description(&self) -> &str { - match *self { - Error::NotFound => "Couldn't locate config file", - Error::Empty => "Empty config file", - Error::ReadingEnvHome(ref err) => err.description(), - Error::Io(ref err) => err.description(), - Error::Yaml(ref err) => err.description(), - } - } -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - Error::NotFound | Error::Empty => { - write!(f, "{}", ::std::error::Error::description(self)) - }, - Error::ReadingEnvHome(ref err) => { - write!(f, "Couldn't read $HOME environment variable: {}", err) - }, - Error::Io(ref err) => write!(f, "Error reading config file: {}", err), - Error::Yaml(ref err) => write!(f, "Problem with config: {}", err), - } - } -} - -impl From<env::VarError> for Error { - fn from(val: env::VarError) -> Error { - Error::ReadingEnvHome(val) - } -} - -impl From<io::Error> for Error { - fn from(val: io::Error) -> Error { - if val.kind() == io::ErrorKind::NotFound { - Error::NotFound - } else { - Error::Io(val) - } - } -} - -impl From<serde_yaml::Error> for Error { - fn from(val: serde_yaml::Error) -> Error { - Error::Yaml(val) - } -} - -/// Result from config loading -pub type Result<T> = ::std::result::Result<T, Error>; - -impl Config { - /// Get the location of the first found default config file paths - /// according to the following order: - /// - /// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml - /// 2. $XDG_CONFIG_HOME/alacritty.yml - /// 3. $HOME/.config/alacritty/alacritty.yml - /// 4. $HOME/.alacritty.yml - #[cfg(not(windows))] - pub fn installed_config<'a>() -> Option<Cow<'a, Path>> { - // Try using XDG location by default - ::xdg::BaseDirectories::with_prefix("alacritty") - .ok() - .and_then(|xdg| xdg.find_config_file("alacritty.yml")) - .or_else(|| { - ::xdg::BaseDirectories::new() - .ok() - .and_then(|fallback| fallback.find_config_file("alacritty.yml")) - }) - .or_else(|| { - if let Ok(home) = env::var("HOME") { - // Fallback path: $HOME/.config/alacritty/alacritty.yml - let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml"); - if fallback.exists() { - return Some(fallback); - } - // Fallback path: $HOME/.alacritty.yml - let fallback = PathBuf::from(&home).join(".alacritty.yml"); - if fallback.exists() { - return Some(fallback); - } - } - None - }) - .map(Into::into) - } - - // TODO: Remove old configuration location warning (Deprecated 03/12/2018) - #[cfg(windows)] - pub fn installed_config<'a>() -> Option<Cow<'a, Path>> { - let old = dirs::home_dir().map(|path| path.join("alacritty.yml")); - let new = dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")); - - if let Some(old_path) = old.as_ref().filter(|old| old.exists()) { - warn!( - "Found configuration at: {}; this file should be moved to the new location: {}", - old_path.to_string_lossy(), - new.as_ref().map(|new| new.to_string_lossy()).unwrap(), - ); - - old.map(Cow::from) - } else { - new.filter(|new| new.exists()).map(Cow::from) - } - } - - #[cfg(not(windows))] - pub fn write_defaults() -> io::Result<Cow<'static, Path>> { - let path = xdg::BaseDirectories::with_prefix("alacritty") - .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err.to_string().as_str())) - .and_then(|p| p.place_config_file("alacritty.yml"))?; - - File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; - - Ok(path.into()) - } - - #[cfg(windows)] - pub fn write_defaults() -> io::Result<Cow<'static, Path>> { - let mut path = dirs::config_dir().ok_or_else(|| { - io::Error::new(io::ErrorKind::NotFound, "Couldn't find profile directory") - })?; - - path = path.join("alacritty/alacritty.yml"); - - std::fs::create_dir_all(path.parent().unwrap())?; - - File::create(&path)?.write_all(DEFAULT_ALACRITTY_CONFIG.as_bytes())?; - - Ok(path.into()) - } - - /// Get list of colors - /// - /// The ordering returned here is expected by the terminal. Colors are simply indexed in this - /// array for performance. - pub fn colors(&self) -> &Colors { - &self.colors - } - - #[inline] - pub fn background_opacity(&self) -> Alpha { - self.background_opacity - } - - pub fn key_bindings(&self) -> &[KeyBinding] { - &self.key_bindings[..] - } - - pub fn mouse_bindings(&self) -> &[MouseBinding] { - &self.mouse_bindings[..] - } - - pub fn mouse(&self) -> &Mouse { - &self.mouse - } - - pub fn selection(&self) -> &Selection { - &self.selection - } - - pub fn tabspaces(&self) -> usize { - self.tabspaces - } - - pub fn padding(&self) -> &Delta<u8> { - self.padding.as_ref().unwrap_or(&self.window.padding) - } - - #[inline] - pub fn draw_bold_text_with_bright_colors(&self) -> bool { - self.draw_bold_text_with_bright_colors - } - - /// Get font config - #[inline] - pub fn font(&self) -> &Font { - &self.font - } - - /// Get window dimensions - #[inline] - pub fn dimensions(&self) -> Dimensions { - self.dimensions.unwrap_or(self.window.dimensions) - } - - /// Get window config - #[inline] - pub fn window(&self) -> &WindowConfig { - &self.window - } - - /// Get visual bell config - #[inline] - pub fn visual_bell(&self) -> &VisualBellConfig { - &self.visual_bell - } - - /// Should show render timer - #[inline] - pub fn render_timer(&self) -> bool { - self.render_timer - } - - #[cfg(target_os = "macos")] - #[inline] - pub fn use_thin_strokes(&self) -> bool { - self.font.use_thin_strokes - } - - #[cfg(not(target_os = "macos"))] - #[inline] - pub fn use_thin_strokes(&self) -> bool { - false - } - - pub fn path(&self) -> Option<&Path> { - self.config_path.as_ref().map(PathBuf::as_path) - } - - pub fn shell(&self) -> Option<&Shell<'_>> { - self.shell.as_ref() - } - - pub fn env(&self) -> &HashMap<String, String> { - &self.env - } - - /// Should hide mouse cursor when typing - #[inline] - pub fn hide_mouse_when_typing(&self) -> bool { - self.hide_cursor_when_typing.unwrap_or(self.mouse.hide_when_typing) - } - - /// Style of the cursor - #[inline] - pub fn cursor_style(&self) -> CursorStyle { - self.cursor_style.unwrap_or(self.cursor.style) - } - - /// Use hollow block cursor when unfocused - #[inline] - pub fn unfocused_hollow_cursor(&self) -> bool { - self.unfocused_hollow_cursor.unwrap_or(self.cursor.unfocused_hollow) - } - - /// Live config reload - #[inline] - pub fn live_config_reload(&self) -> bool { - self.live_config_reload - } - - #[inline] - pub fn dynamic_title(&self) -> bool { - self.dynamic_title - } - - /// Scrolling settings - #[inline] - pub fn scrolling(&self) -> Scrolling { - self.scrolling - } - - /// Cursor foreground color - #[inline] - pub fn cursor_text_color(&self) -> Option<Rgb> { - self.colors.cursor.text - } - - /// Cursor background color - #[inline] - pub fn cursor_cursor_color(&self) -> Option<Rgb> { - self.colors.cursor.cursor - } - - /// Enable experimental conpty backend (Windows only) - #[cfg(windows)] - #[inline] - pub fn enable_experimental_conpty_backend(&self) -> bool { - self.enable_experimental_conpty_backend - } - - /// Send escape sequences using the alt key - #[inline] - pub fn alt_send_esc(&self) -> bool { - self.alt_send_esc - } - - // Update the history size, used in ref tests - pub fn set_history(&mut self, history: u32) { - self.scrolling.history = history; - } - - /// Keep the log file after quitting Alacritty - #[inline] - pub fn persistent_logging(&self) -> bool { - self.persistent_logging - } - - /// Overrides the `dynamic_title` configuration based on `--title`. - pub fn update_dynamic_title(mut self, options: &Options) -> Self { - if options.title.is_some() { - self.dynamic_title = false; - } - self - } - - pub fn load_from(path: PathBuf) -> Config { - let mut config = Config::reload_from(&path).unwrap_or_else(|_| Config::default()); - config.config_path = Some(path); - config - } - - pub fn reload_from(path: &PathBuf) -> Result<Config> { - match Config::read_config(path) { - Ok(config) => Ok(config), - Err(err) => { - error!("Unable to load config {:?}: {}", path, err); - Err(err) - }, - } - } - - fn read_config(path: &PathBuf) -> Result<Config> { - let mut contents = String::new(); - File::open(path)?.read_to_string(&mut contents)?; - - // Prevent parsing error with empty string - if contents.is_empty() { - return Ok(Config::default()); - } - - let mut config: Config = serde_yaml::from_str(&contents)?; - config.print_deprecation_warnings(); - - Ok(config) - } - - fn print_deprecation_warnings(&mut self) { - if self.dimensions.is_some() { - warn!("Config dimensions is deprecated; please use window.dimensions instead"); - } - - if self.padding.is_some() { - warn!("Config padding is deprecated; please use window.padding instead"); - } - - if self.mouse.faux_scrollback_lines.is_some() { - warn!( - "Config mouse.faux_scrollback_lines is deprecated; please use \ - mouse.faux_scrolling_lines instead" - ); - } - - if let Some(custom_cursor_colors) = self.custom_cursor_colors { - warn!("Config custom_cursor_colors is deprecated"); - - if !custom_cursor_colors { - self.colors.cursor.cursor = None; - self.colors.cursor.text = None; - } - } - - if self.cursor_style.is_some() { - warn!("Config cursor_style is deprecated; please use cursor.style instead"); - } - - if self.hide_cursor_when_typing.is_some() { - warn!( - "Config hide_cursor_when_typing is deprecated; please use mouse.hide_when_typing \ - instead" - ); - } - - if self.unfocused_hollow_cursor.is_some() { - warn!( - "Config unfocused_hollow_cursor is deprecated; please use cursor.unfocused_hollow \ - instead" - ); - } - - if let Some(start_maximized) = self.window.start_maximized { - warn!( - "Config window.start_maximized is deprecated; please use window.startup_mode \ - instead" - ); - - // While `start_maximized` is deprecated its setting takes precedence. - if start_maximized { - self.window.startup_mode = StartupMode::Maximized; - } - } - } -} - -/// Window Dimensions -/// -/// Newtype to avoid passing values incorrectly -#[serde(default)] -#[derive(Default, Debug, Copy, Clone, Deserialize, PartialEq, Eq)] -pub struct Dimensions { - /// Window width in character columns - #[serde(deserialize_with = "failure_default")] - columns: Column, - - /// Window Height in character lines - #[serde(deserialize_with = "failure_default")] - lines: Line, -} - -impl Dimensions { - pub fn new(columns: Column, lines: Line) -> Self { - Dimensions { columns, lines } - } - - /// Get lines - #[inline] - pub fn lines_u32(&self) -> u32 { - self.lines.0 as u32 - } - - /// Get columns - #[inline] - pub fn columns_u32(&self) -> u32 { - self.columns.0 as u32 - } -} - -/// A delta for a point in a 2 dimensional plane -#[serde(default, bound(deserialize = "T: Deserialize<'de> + Default"))] -#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] -pub struct Delta<T: Default + PartialEq + Eq> { - /// Horizontal change - #[serde(deserialize_with = "failure_default")] - pub x: T, - /// Vertical change - #[serde(deserialize_with = "failure_default")] - pub y: T, -} - -trait DeserializeSize: Sized { - fn deserialize<'a, D>(_: D) -> ::std::result::Result<Self, D::Error> - where - D: serde::de::Deserializer<'a>; -} - -impl DeserializeSize for Size { - fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result<Self, D::Error> - where - D: serde::de::Deserializer<'a>, - { - use std::marker::PhantomData; - - struct NumVisitor<__D> { - _marker: PhantomData<__D>, - } - - impl<'a, __D> Visitor<'a> for NumVisitor<__D> - where - __D: serde::de::Deserializer<'a>, - { - type Value = f64; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("f64 or u64") - } - - fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Self::Value, E> - where - E: ::serde::de::Error, - { - Ok(value) - } - - fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Self::Value, E> - where - E: ::serde::de::Error, - { - Ok(value as f64) - } - } - - let size = deserializer - .deserialize_any(NumVisitor::<D> { _marker: PhantomData }) - .map(|v| Size::new(v as _)); - - // Use default font size as fallback - match size { - Ok(size) => Ok(size), - Err(err) => { - let size = default_font_size(); - error!("Problem with config: {}; using size {}", err, size.as_f32_pts()); - Ok(size) - }, - } - } -} - -/// Font config -/// -/// Defaults are provided at the level of this struct per platform, but not per -/// field in this struct. It might be nice in the future to have defaults for -/// each value independently. Alternatively, maybe erroring when the user -/// doesn't provide complete config is Ok. -#[serde(default)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub struct Font { - /// Normal font face - #[serde(deserialize_with = "failure_default")] - normal: FontDescription, - - /// Bold font face - #[serde(deserialize_with = "failure_default")] - italic: SecondaryFontDescription, - - /// Italic font face - #[serde(deserialize_with = "failure_default")] - bold: SecondaryFontDescription, - - /// Font size in points - #[serde(deserialize_with = "DeserializeSize::deserialize")] - pub size: Size, - - /// Extra spacing per character - #[serde(deserialize_with = "failure_default")] - offset: Delta<i8>, - - /// Glyph offset within character cell - #[serde(deserialize_with = "failure_default")] - glyph_offset: Delta<i8>, - - #[cfg(target_os = "macos")] - #[serde(deserialize_with = "deserialize_true_bool")] - use_thin_strokes: bool, - - // TODO: Deprecated - #[serde(deserialize_with = "deserialize_scale_with_dpi")] - scale_with_dpi: Option<()>, -} - -impl Default for Font { - fn default() -> Font { - Font { - #[cfg(target_os = "macos")] - use_thin_strokes: true, - size: default_font_size(), - normal: Default::default(), - bold: Default::default(), - italic: Default::default(), - scale_with_dpi: Default::default(), - glyph_offset: Default::default(), - offset: Default::default(), - } - } -} - -impl Font { - /// Get the font size in points - #[inline] - pub fn size(&self) -> Size { - self.size - } - - /// Get offsets to font metrics - #[inline] - pub fn offset(&self) -> &Delta<i8> { - &self.offset - } - - /// Get cell offsets for glyphs - #[inline] - pub fn glyph_offset(&self) -> &Delta<i8> { - &self.glyph_offset - } - - /// Get a font clone with a size modification - pub fn with_size(self, size: Size) -> Font { - Font { size, ..self } - } - - // Get normal font description - pub fn normal(&self) -> &FontDescription { - &self.normal - } - - // Get italic font description - pub fn italic(&self) -> FontDescription { - self.italic.desc(&self.normal) - } - - // Get bold font description - pub fn bold(&self) -> FontDescription { - self.bold.desc(&self.normal) - } -} - -fn default_font_size() -> Size { - Size::new(11.) -} - -fn deserialize_scale_with_dpi<'a, D>(deserializer: D) -> ::std::result::Result<Option<()>, D::Error> -where - D: de::Deserializer<'a>, -{ - // This is necessary in order to get serde to complete deserialization of the configuration - let _ignored = bool::deserialize(deserializer); - error!( - "The scale_with_dpi setting has been removed, on X11 the WINIT_HIDPI_FACTOR environment \ - variable can be used instead." - ); - Ok(None) -} - -/// Description of the normal font -#[serde(default)] -#[derive(Debug, Deserialize, Clone, PartialEq, Eq)] -pub struct FontDescription { - #[serde(deserialize_with = "failure_default")] - pub family: String, - #[serde(deserialize_with = "failure_default")] - pub style: Option<String>, -} - -impl Default for FontDescription { - fn default() -> FontDescription { - FontDescription { - #[cfg(not(any(target_os = "macos", windows)))] - family: "monospace".into(), - #[cfg(target_os = "macos")] - family: "Menlo".into(), - #[cfg(windows)] - family: "Consolas".into(), - style: None, - } - } -} - -/// Description of the italic and bold font -#[serde(default)] -#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq)] -pub struct SecondaryFontDescription { - #[serde(deserialize_with = "failure_default")] - family: Option<String>, - #[serde(deserialize_with = "failure_default")] - style: Option<String>, -} - -impl SecondaryFontDescription { - pub fn desc(&self, fallback: &FontDescription) -> FontDescription { - FontDescription { - family: self.family.clone().unwrap_or_else(|| fallback.family.clone()), - style: self.style.clone(), - } - } -} - -pub struct Monitor { - _thread: ::std::thread::JoinHandle<()>, - rx: mpsc::Receiver<PathBuf>, -} - -pub trait OnConfigReload { - fn on_config_reload(&mut self); -} - -impl OnConfigReload for crate::display::Notifier { - fn on_config_reload(&mut self) { - self.notify(); - } -} - -impl Monitor { - /// Get pending config changes - pub fn pending(&self) -> Option<PathBuf> { - let mut config = None; - while let Ok(new) = self.rx.try_recv() { - config = Some(new); - } - - config - } - - pub fn new<H, P>(path: P, mut handler: H) -> Monitor - where - H: OnConfigReload + Send + 'static, - P: Into<PathBuf>, - { - let path = path.into(); - - let (config_tx, config_rx) = mpsc::channel(); - - Monitor { - _thread: crate::util::thread::spawn_named("config watcher", move || { - let (tx, rx) = mpsc::channel(); - // The Duration argument is a debouncing period. - let mut watcher = - watcher(tx, Duration::from_millis(10)).expect("Unable to spawn file watcher"); - let config_path = ::std::fs::canonicalize(path).expect("canonicalize config path"); - - // Get directory of config - let mut parent = config_path.clone(); - parent.pop(); - - // Watch directory - watcher - .watch(&parent, RecursiveMode::NonRecursive) - .expect("watch alacritty.yml dir"); - - loop { - match rx.recv().expect("watcher event") { - DebouncedEvent::Rename(..) => continue, - DebouncedEvent::Write(path) - | DebouncedEvent::Create(path) - | DebouncedEvent::Chmod(path) => { - if path != config_path { - continue; - } - - let _ = config_tx.send(path); - handler.on_config_reload(); - }, - _ => {}, - } - } - }), - rx: config_rx, - } - } -} - -#[derive(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub enum Key { - Scancode(u32), - Key1, - Key2, - Key3, - Key4, - Key5, - Key6, - Key7, - Key8, - Key9, - Key0, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - Escape, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - F13, - F14, - F15, - F16, - F17, - F18, - F19, - F20, - F21, - F22, - F23, - F24, - Snapshot, - Scroll, - Pause, - Insert, - Home, - Delete, - End, - PageDown, - PageUp, - Left, - Up, - Right, - Down, - Back, - Return, - Space, - Compose, - Numlock, - Numpad0, - Numpad1, - Numpad2, - Numpad3, - Numpad4, - Numpad5, - Numpad6, - Numpad7, - Numpad8, - Numpad9, - AbntC1, - AbntC2, - Add, - Apostrophe, - Apps, - At, - Ax, - Backslash, - Calculator, - Capital, - Colon, - Comma, - Convert, - Decimal, - Divide, - Equals, - Grave, - Kana, - Kanji, - LAlt, - LBracket, - LControl, - LShift, - LWin, - Mail, - MediaSelect, - MediaStop, - Minus, - Multiply, - Mute, - MyComputer, - NavigateForward, - NavigateBackward, - NextTrack, - NoConvert, - NumpadComma, - NumpadEnter, - NumpadEquals, - OEM102, - Period, - PlayPause, - Power, - PrevTrack, - RAlt, - RBracket, - RControl, - RShift, - RWin, - Semicolon, - Slash, - Sleep, - Stop, - Subtract, - Sysrq, - Tab, - Underline, - Unlabeled, - VolumeDown, - VolumeUp, - Wake, - WebBack, - WebFavorites, - WebForward, - WebHome, - WebRefresh, - WebSearch, - WebStop, - Yen, - Caret, - Copy, - Paste, - Cut, -} - -impl Key { - pub fn from_glutin_input(key: ::glutin::VirtualKeyCode) -> Self { - use glutin::VirtualKeyCode::*; - // Thank you, vim macros and regex! - match key { - Key1 => Key::Key1, - Key2 => Key::Key2, - Key3 => Key::Key3, - Key4 => Key::Key4, - Key5 => Key::Key5, - Key6 => Key::Key6, - Key7 => Key::Key7, - Key8 => Key::Key8, - Key9 => Key::Key9, - Key0 => Key::Key0, - A => Key::A, - B => Key::B, - C => Key::C, - D => Key::D, - E => Key::E, - F => Key::F, - G => Key::G, - H => Key::H, - I => Key::I, - J => Key::J, - K => Key::K, - L => Key::L, - M => Key::M, - N => Key::N, - O => Key::O, - P => Key::P, - Q => Key::Q, - R => Key::R, - S => Key::S, - T => Key::T, - U => Key::U, - V => Key::V, - W => Key::W, - X => Key::X, - Y => Key::Y, - Z => Key::Z, - Escape => Key::Escape, - F1 => Key::F1, - F2 => Key::F2, - F3 => Key::F3, - F4 => Key::F4, - F5 => Key::F5, - F6 => Key::F6, - F7 => Key::F7, - F8 => Key::F8, - F9 => Key::F9, - F10 => Key::F10, - F11 => Key::F11, - F12 => Key::F12, - F13 => Key::F13, - F14 => Key::F14, - F15 => Key::F15, - F16 => Key::F16, - F17 => Key::F17, - F18 => Key::F18, - F19 => Key::F19, - F20 => Key::F20, - F21 => Key::F21, - F22 => Key::F22, - F23 => Key::F23, - F24 => Key::F24, - Snapshot => Key::Snapshot, - Scroll => Key::Scroll, - Pause => Key::Pause, - Insert => Key::Insert, - Home => Key::Home, - Delete => Key::Delete, - End => Key::End, - PageDown => Key::PageDown, - PageUp => Key::PageUp, - Left => Key::Left, - Up => Key::Up, - Right => Key::Right, - Down => Key::Down, - Back => Key::Back, - Return => Key::Return, - Space => Key::Space, - Compose => Key::Compose, - Numlock => Key::Numlock, - Numpad0 => Key::Numpad0, - Numpad1 => Key::Numpad1, - Numpad2 => Key::Numpad2, - Numpad3 => Key::Numpad3, - Numpad4 => Key::Numpad4, - Numpad5 => Key::Numpad5, - Numpad6 => Key::Numpad6, - Numpad7 => Key::Numpad7, - Numpad8 => Key::Numpad8, - Numpad9 => Key::Numpad9, - AbntC1 => Key::AbntC1, - AbntC2 => Key::AbntC2, - Add => Key::Add, - Apostrophe => Key::Apostrophe, - Apps => Key::Apps, - At => Key::At, - Ax => Key::Ax, - Backslash => Key::Backslash, - Calculator => Key::Calculator, - Capital => Key::Capital, - Colon => Key::Colon, - Comma => Key::Comma, - Convert => Key::Convert, - Decimal => Key::Decimal, - Divide => Key::Divide, - Equals => Key::Equals, - Grave => Key::Grave, - Kana => Key::Kana, - Kanji => Key::Kanji, - LAlt => Key::LAlt, - LBracket => Key::LBracket, - LControl => Key::LControl, - LShift => Key::LShift, - LWin => Key::LWin, - Mail => Key::Mail, - MediaSelect => Key::MediaSelect, - MediaStop => Key::MediaStop, - Minus => Key::Minus, - Multiply => Key::Multiply, - Mute => Key::Mute, - MyComputer => Key::MyComputer, - NavigateForward => Key::NavigateForward, - NavigateBackward => Key::NavigateBackward, - NextTrack => Key::NextTrack, - NoConvert => Key::NoConvert, - NumpadComma => Key::NumpadComma, - NumpadEnter => Key::NumpadEnter, - NumpadEquals => Key::NumpadEquals, - OEM102 => Key::OEM102, - Period => Key::Period, - PlayPause => Key::PlayPause, - Power => Key::Power, - PrevTrack => Key::PrevTrack, - RAlt => Key::RAlt, - RBracket => Key::RBracket, - RControl => Key::RControl, - RShift => Key::RShift, - RWin => Key::RWin, - Semicolon => Key::Semicolon, - Slash => Key::Slash, - Sleep => Key::Sleep, - Stop => Key::Stop, - Subtract => Key::Subtract, - Sysrq => Key::Sysrq, - Tab => Key::Tab, - Underline => Key::Underline, - Unlabeled => Key::Unlabeled, - VolumeDown => Key::VolumeDown, - VolumeUp => Key::VolumeUp, - Wake => Key::Wake, - WebBack => Key::WebBack, - WebFavorites => Key::WebFavorites, - WebForward => Key::WebForward, - WebHome => Key::WebHome, - WebRefresh => Key::WebRefresh, - WebSearch => Key::WebSearch, - WebStop => Key::WebStop, - Yen => Key::Yen, - Caret => Key::Caret, - Copy => Key::Copy, - Paste => Key::Paste, - Cut => Key::Cut, - } - } -} - -#[cfg(test)] -mod tests { - use super::{Config, DEFAULT_ALACRITTY_CONFIG}; - use crate::cli::Options; - - #[test] - fn parse_config() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - - // Sanity check that mouse bindings are being parsed - assert!(!config.mouse_bindings.is_empty()); - - // Sanity check that key bindings are being parsed - assert!(!config.key_bindings.is_empty()); - } - - #[test] - fn dynamic_title_ignoring_options_by_default() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - let old_dynamic_title = config.dynamic_title; - let options = Options::default(); - let config = config.update_dynamic_title(&options); - assert_eq!(old_dynamic_title, config.dynamic_title); - } - - #[test] - fn dynamic_title_overridden_by_options() { - let config: Config = - ::serde_yaml::from_str(DEFAULT_ALACRITTY_CONFIG).expect("deserialize config"); - let mut options = Options::default(); - options.title = Some("foo".to_owned()); - let config = config.update_dynamic_title(&options); - assert!(!config.dynamic_title); - } - - #[test] - fn default_match_empty() { - let default = Config::default(); - - let empty = serde_yaml::from_str("key: val\n").unwrap(); - - assert_eq!(default, empty); - } -} diff --git a/src/cursor.rs b/src/cursor.rs deleted file mode 100644 index 196241a0..00000000 --- a/src/cursor.rs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Helpers for creating different cursor glyphs from font metrics - -use std::cmp; - -use font::{Metrics, RasterizedGlyph}; - -use crate::ansi::CursorStyle; - -/// Width/Height of the cursor relative to the font width -pub const CURSOR_WIDTH_PERCENTAGE: i32 = 15; - -pub fn get_cursor_glyph( - cursor: CursorStyle, - metrics: Metrics, - offset_x: i8, - offset_y: i8, - is_wide: bool, -) -> RasterizedGlyph { - // Calculate the cell metrics - let height = metrics.line_height as i32 + i32::from(offset_y); - let mut width = metrics.average_advance as i32 + i32::from(offset_x); - let line_width = cmp::max(width * CURSOR_WIDTH_PERCENTAGE / 100, 1); - - // Double the cursor width if it's above a double-width glyph - if is_wide { - width *= 2; - } - - match cursor { - CursorStyle::HollowBlock => get_box_cursor_glyph(height, width, line_width), - CursorStyle::Underline => get_underline_cursor_glyph(width, line_width), - CursorStyle::Beam => get_beam_cursor_glyph(height, line_width), - CursorStyle::Block => get_block_cursor_glyph(height, width), - CursorStyle::Hidden => RasterizedGlyph::default(), - } -} - -// Returns a custom underline cursor character -pub fn get_underline_cursor_glyph(width: i32, line_width: i32) -> RasterizedGlyph { - // Create a new rectangle, the height is relative to the font width - let buf = vec![255u8; (width * line_width * 3) as usize]; - - // Create a custom glyph with the rectangle data attached to it - RasterizedGlyph { c: ' ', top: line_width, left: 0, height: line_width, width, buf } -} - -// Returns a custom beam cursor character -pub fn get_beam_cursor_glyph(height: i32, line_width: i32) -> RasterizedGlyph { - // Create a new rectangle that is at least one pixel wide - let buf = vec![255u8; (line_width * height * 3) as usize]; - - // Create a custom glyph with the rectangle data attached to it - RasterizedGlyph { c: ' ', top: height, left: 0, height, width: line_width, buf } -} - -// Returns a custom box cursor character -pub fn get_box_cursor_glyph(height: i32, width: i32, line_width: i32) -> RasterizedGlyph { - // Create a new box outline rectangle - let mut buf = Vec::with_capacity((width * height * 3) as usize); - for y in 0..height { - for x in 0..width { - if y < line_width - || y >= height - line_width - || x < line_width - || x >= width - line_width - { - buf.append(&mut vec![255u8; 3]); - } else { - buf.append(&mut vec![0u8; 3]); - } - } - } - - // Create a custom glyph with the rectangle data attached to it - RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf } -} - -// Returns a custom block cursor character -pub fn get_block_cursor_glyph(height: i32, width: i32) -> RasterizedGlyph { - // Create a completely filled glyph - let buf = vec![255u8; (width * height * 3) as usize]; - - // Create a custom glyph with the rectangle data attached to it - RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf } -} diff --git a/src/display.rs b/src/display.rs deleted file mode 100644 index 1d5799f6..00000000 --- a/src/display.rs +++ /dev/null @@ -1,560 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! The display subsystem including window management, font rasterization, and -//! GPU drawing. -use std::f64; -use std::sync::mpsc; - -use glutin::dpi::{PhysicalPosition, PhysicalSize}; -use glutin::EventsLoop; -use parking_lot::MutexGuard; - -use crate::cli; -use crate::config::{Config, StartupMode}; -use crate::index::Line; -use crate::message_bar::Message; -use crate::meter::Meter; -use crate::renderer::rects::{Rect, Rects}; -use crate::renderer::{self, GlyphCache, QuadRenderer}; -use crate::sync::FairMutex; -use crate::term::color::Rgb; -use crate::term::{RenderableCell, SizeInfo, Term}; -use crate::window::{self, Window}; -use font::{self, Rasterize}; - -#[derive(Debug)] -pub enum Error { - /// Error with window management - Window(window::Error), - - /// Error dealing with fonts - Font(font::Error), - - /// Error in renderer - Render(renderer::Error), -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - Error::Window(ref err) => Some(err), - Error::Font(ref err) => Some(err), - Error::Render(ref err) => Some(err), - } - } - - fn description(&self) -> &str { - match *self { - Error::Window(ref err) => err.description(), - Error::Font(ref err) => err.description(), - Error::Render(ref err) => err.description(), - } - } -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - Error::Window(ref err) => err.fmt(f), - Error::Font(ref err) => err.fmt(f), - Error::Render(ref err) => err.fmt(f), - } - } -} - -impl From<window::Error> for Error { - fn from(val: window::Error) -> Error { - Error::Window(val) - } -} - -impl From<font::Error> for Error { - fn from(val: font::Error) -> Error { - Error::Font(val) - } -} - -impl From<renderer::Error> for Error { - fn from(val: renderer::Error) -> Error { - Error::Render(val) - } -} - -/// The display wraps a window, font rasterizer, and GPU renderer -pub struct Display { - window: Window, - renderer: QuadRenderer, - glyph_cache: GlyphCache, - render_timer: bool, - rx: mpsc::Receiver<PhysicalSize>, - tx: mpsc::Sender<PhysicalSize>, - meter: Meter, - font_size: font::Size, - size_info: SizeInfo, - last_message: Option<Message>, -} - -/// Can wakeup the render loop from other threads -pub struct Notifier(window::Proxy); - -/// Types that are interested in when the display is resized -pub trait OnResize { - fn on_resize(&mut self, size: &SizeInfo); -} - -impl Notifier { - pub fn notify(&self) { - self.0.wakeup_event_loop(); - } -} - -impl Display { - pub fn notifier(&self) -> Notifier { - Notifier(self.window.create_window_proxy()) - } - - pub fn update_config(&mut self, config: &Config) { - self.render_timer = config.render_timer(); - } - - /// Get size info about the display - pub fn size(&self) -> &SizeInfo { - &self.size_info - } - - pub fn new(config: &Config, options: &cli::Options) -> Result<Display, Error> { - // Extract some properties from config - let render_timer = config.render_timer(); - - // Guess DPR based on first monitor - let event_loop = EventsLoop::new(); - let estimated_dpr = - event_loop.get_available_monitors().next().map(|m| m.get_hidpi_factor()).unwrap_or(1.); - - // Guess the target window dimensions - let metrics = GlyphCache::static_metrics(config, estimated_dpr as f32)?; - let (cell_width, cell_height) = Self::compute_cell_size(config, &metrics); - let dimensions = - Self::calculate_dimensions(config, options, estimated_dpr, cell_width, cell_height); - - debug!("Estimated DPR: {}", estimated_dpr); - debug!("Estimated Cell Size: {} x {}", cell_width, cell_height); - debug!("Estimated Dimensions: {:?}", dimensions); - - // Create the window where Alacritty will be displayed - let logical = dimensions.map(|d| PhysicalSize::new(d.0, d.1).to_logical(estimated_dpr)); - let mut window = Window::new(event_loop, &options, config.window(), logical)?; - - let dpr = window.hidpi_factor(); - info!("Device pixel ratio: {}", dpr); - - // get window properties for initializing the other subsystems - let mut viewport_size = - window.inner_size_pixels().expect("glutin returns window size").to_physical(dpr); - - // Create renderer - let mut renderer = QuadRenderer::new()?; - - let (glyph_cache, cell_width, cell_height) = - Self::new_glyph_cache(dpr, &mut renderer, config)?; - - let mut padding_x = f64::from(config.padding().x) * dpr; - let mut padding_y = f64::from(config.padding().y) * dpr; - - if let Some((width, height)) = - Self::calculate_dimensions(config, options, dpr, cell_width, cell_height) - { - if dimensions == Some((width, height)) { - info!("Estimated DPR correctly, skipping resize"); - } else { - viewport_size = PhysicalSize::new(width, height); - window.set_inner_size(viewport_size.to_logical(dpr)); - } - } else if config.window().dynamic_padding() { - // Make sure additional padding is spread evenly - let cw = f64::from(cell_width); - let ch = f64::from(cell_height); - padding_x = padding_x + (viewport_size.width - 2. * padding_x) % cw / 2.; - padding_y = padding_y + (viewport_size.height - 2. * padding_y) % ch / 2.; - } - - padding_x = padding_x.floor(); - padding_y = padding_y.floor(); - - // Update OpenGL projection - renderer.resize(viewport_size, padding_x as f32, padding_y as f32); - - info!("Cell Size: {} x {}", cell_width, cell_height); - info!("Padding: {} x {}", padding_x, padding_y); - - let size_info = SizeInfo { - dpr, - width: viewport_size.width as f32, - height: viewport_size.height as f32, - cell_width: cell_width as f32, - cell_height: cell_height as f32, - padding_x: padding_x as f32, - padding_y: padding_y as f32, - }; - - // Channel for resize events - // - // macOS has a callback for getting resize events, the channel is used - // to queue resize events until the next draw call. Unfortunately, it - // seems that the event loop is blocked until the window is done - // resizing. If any drawing were to happen during a resize, it would - // need to be in the callback. - let (tx, rx) = mpsc::channel(); - - // Clear screen - let background_color = config.colors().primary.background; - renderer.with_api(config, &size_info, |api| { - api.clear(background_color); - }); - - Ok(Display { - window, - renderer, - glyph_cache, - render_timer, - tx, - rx, - meter: Meter::new(), - font_size: config.font().size(), - size_info, - last_message: None, - }) - } - - fn calculate_dimensions( - config: &Config, - options: &cli::Options, - dpr: f64, - cell_width: f32, - cell_height: f32, - ) -> Option<(f64, f64)> { - let dimensions = options.dimensions().unwrap_or_else(|| config.dimensions()); - - if dimensions.columns_u32() == 0 - || dimensions.lines_u32() == 0 - || config.window().startup_mode() != StartupMode::Windowed - { - return None; - } - - let padding_x = f64::from(config.padding().x) * dpr; - let padding_y = f64::from(config.padding().y) * dpr; - - // Calculate new size based on cols/lines specified in config - let grid_width = cell_width as u32 * dimensions.columns_u32(); - let grid_height = cell_height as u32 * dimensions.lines_u32(); - - let width = (f64::from(grid_width) + 2. * padding_x).floor(); - let height = (f64::from(grid_height) + 2. * padding_y).floor(); - - Some((width, height)) - } - - fn new_glyph_cache( - dpr: f64, - renderer: &mut QuadRenderer, - config: &Config, - ) -> Result<(GlyphCache, f32, f32), Error> { - let font = config.font().clone(); - let rasterizer = font::Rasterizer::new(dpr as f32, config.use_thin_strokes())?; - - // Initialize glyph cache - let glyph_cache = { - info!("Initializing glyph cache..."); - let init_start = ::std::time::Instant::now(); - - let cache = - renderer.with_loader(|mut api| GlyphCache::new(rasterizer, &font, &mut api))?; - - let stop = init_start.elapsed(); - let stop_f = stop.as_secs() as f64 + f64::from(stop.subsec_nanos()) / 1_000_000_000f64; - info!("... finished initializing glyph cache in {}s", stop_f); - - cache - }; - - // Need font metrics to resize the window properly. This suggests to me the - // font metrics should be computed before creating the window in the first - // place so that a resize is not needed. - let (cw, ch) = Self::compute_cell_size(config, &glyph_cache.font_metrics()); - - Ok((glyph_cache, cw, ch)) - } - - pub fn update_glyph_cache(&mut self, config: &Config) { - let cache = &mut self.glyph_cache; - let dpr = self.size_info.dpr; - let size = self.font_size; - - self.renderer.with_loader(|mut api| { - let _ = cache.update_font_size(config.font(), size, dpr, &mut api); - }); - - let (cw, ch) = Self::compute_cell_size(config, &cache.font_metrics()); - self.size_info.cell_width = cw; - self.size_info.cell_height = ch; - } - - fn compute_cell_size(config: &Config, metrics: &font::Metrics) -> (f32, f32) { - let offset_x = f64::from(config.font().offset().x); - let offset_y = f64::from(config.font().offset().y); - ( - f32::max(1., ((metrics.average_advance + offset_x) as f32).floor()), - f32::max(1., ((metrics.line_height + offset_y) as f32).floor()), - ) - } - - #[inline] - pub fn resize_channel(&self) -> mpsc::Sender<PhysicalSize> { - self.tx.clone() - } - - pub fn window(&mut self) -> &mut Window { - &mut self.window - } - - /// Process pending resize events - pub fn handle_resize( - &mut self, - terminal: &mut MutexGuard<'_, Term>, - config: &Config, - pty_resize_handle: &mut dyn OnResize, - processor_resize_handle: &mut dyn OnResize, - ) { - let previous_cols = self.size_info.cols(); - let previous_lines = self.size_info.lines(); - - // Resize events new_size and are handled outside the poll_events - // iterator. This has the effect of coalescing multiple resize - // events into one. - let mut new_size = None; - - // Take most recent resize event, if any - while let Ok(size) = self.rx.try_recv() { - new_size = Some(size); - } - - // Update the DPR - let dpr = self.window.hidpi_factor(); - - // Font size/DPI factor modification detected - let font_changed = - terminal.font_size != self.font_size || (dpr - self.size_info.dpr).abs() > f64::EPSILON; - - // Skip resize if nothing changed - if let Some(new_size) = new_size { - if !font_changed - && (new_size.width - f64::from(self.size_info.width)).abs() < f64::EPSILON - && (new_size.height - f64::from(self.size_info.height)).abs() < f64::EPSILON - { - return; - } - } - - if font_changed || self.last_message != terminal.message_buffer_mut().message() { - if new_size == None { - // Force a resize to refresh things - new_size = Some(PhysicalSize::new( - f64::from(self.size_info.width) / self.size_info.dpr * dpr, - f64::from(self.size_info.height) / self.size_info.dpr * dpr, - )); - } - - self.font_size = terminal.font_size; - self.last_message = terminal.message_buffer_mut().message(); - self.size_info.dpr = dpr; - } - - if font_changed { - self.update_glyph_cache(config); - } - - if let Some(psize) = new_size.take() { - let width = psize.width as f32; - let height = psize.height as f32; - let cell_width = self.size_info.cell_width; - let cell_height = self.size_info.cell_height; - - self.size_info.width = width; - self.size_info.height = height; - - let mut padding_x = f32::from(config.padding().x) * dpr as f32; - let mut padding_y = f32::from(config.padding().y) * dpr as f32; - - if config.window().dynamic_padding() { - padding_x = padding_x + ((width - 2. * padding_x) % cell_width) / 2.; - padding_y = padding_y + ((height - 2. * padding_y) % cell_height) / 2.; - } - - self.size_info.padding_x = padding_x.floor(); - self.size_info.padding_y = padding_y.floor(); - - let size = &self.size_info; - terminal.resize(size); - processor_resize_handle.on_resize(size); - - // Subtract message bar lines for pty size - let mut pty_size = *size; - if let Some(message) = terminal.message_buffer_mut().message() { - pty_size.height -= pty_size.cell_height * message.text(&size).len() as f32; - } - - if previous_cols != pty_size.cols() || previous_lines != pty_size.lines() { - pty_resize_handle.on_resize(&pty_size); - } - - self.window.resize(psize); - self.renderer.resize(psize, self.size_info.padding_x, self.size_info.padding_y); - } - } - - /// Draw the screen - /// - /// A reference to Term whose state is being drawn must be provided. - /// - /// This call may block if vsync is enabled - pub fn draw(&mut self, terminal: &FairMutex<Term>, config: &Config) { - let mut terminal = terminal.lock(); - let size_info = *terminal.size_info(); - let visual_bell_intensity = terminal.visual_bell.intensity(); - let background_color = terminal.background_color(); - let metrics = self.glyph_cache.font_metrics(); - - let window_focused = self.window.is_focused; - let grid_cells: Vec<RenderableCell> = - terminal.renderable_cells(config, window_focused, metrics).collect(); - - // Get message from terminal to ignore modifications after lock is dropped - let message_buffer = terminal.message_buffer_mut().message(); - - // Clear dirty flag - terminal.dirty = !terminal.visual_bell.completed(); - - if let Some(title) = terminal.get_next_title() { - self.window.set_title(&title); - } - - if let Some(mouse_cursor) = terminal.get_next_mouse_cursor() { - self.window.set_mouse_cursor(mouse_cursor); - } - - if let Some(is_urgent) = terminal.next_is_urgent.take() { - // We don't need to set the urgent flag if we already have the - // user's attention. - if !is_urgent || !self.window.is_focused { - self.window.set_urgent(is_urgent); - } - } - - // Clear when terminal mutex isn't held. Mesa for - // some reason takes a long time to call glClear(). The driver descends - // into xcb_connect_to_fd() which ends up calling __poll_nocancel() - // which blocks for a while. - // - // By keeping this outside of the critical region, the Mesa bug is - // worked around to some extent. Since this doesn't actually address the - // issue of glClear being slow, less time is available for input - // handling and rendering. - drop(terminal); - - self.renderer.with_api(config, &size_info, |api| { - api.clear(background_color); - }); - - { - let glyph_cache = &mut self.glyph_cache; - let mut rects = Rects::new(&metrics, &size_info); - - // Draw grid - { - let _sampler = self.meter.sampler(); - - self.renderer.with_api(config, &size_info, |mut api| { - // Iterate over all non-empty cells in the grid - for cell in grid_cells { - // Update underline/strikeout - rects.update_lines(&size_info, &cell); - - // Draw the cell - api.render_cell(cell, glyph_cache); - } - }); - } - - if let Some(message) = message_buffer { - let text = message.text(&size_info); - - // Create a new rectangle for the background - let start_line = size_info.lines().0 - text.len(); - let y = size_info.padding_y + size_info.cell_height * start_line as f32; - let rect = Rect::new(0., y, size_info.width, size_info.height - y); - rects.push(rect, message.color()); - - // Draw rectangles including the new background - self.renderer.draw_rects(config, &size_info, visual_bell_intensity, rects); - - // Relay messages to the user - let mut offset = 1; - for message_text in text.iter().rev() { - self.renderer.with_api(config, &size_info, |mut api| { - api.render_string( - &message_text, - Line(size_info.lines().saturating_sub(offset)), - glyph_cache, - None, - ); - }); - offset += 1; - } - } else { - // Draw rectangles - self.renderer.draw_rects(config, &size_info, visual_bell_intensity, rects); - } - - // Draw render timer - if self.render_timer { - let timing = format!("{:.3} usec", self.meter.average()); - let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; - self.renderer.with_api(config, &size_info, |mut api| { - api.render_string(&timing[..], size_info.lines() - 2, glyph_cache, Some(color)); - }); - } - } - - self.window.swap_buffers().expect("swap buffers"); - } - - pub fn get_window_id(&self) -> Option<usize> { - self.window.get_window_id() - } - - /// Adjust the IME editor position according to the new location of the cursor - pub fn update_ime_position(&mut self, terminal: &Term) { - let point = terminal.cursor().point; - let SizeInfo { cell_width: cw, cell_height: ch, padding_x: px, padding_y: py, .. } = - *terminal.size_info(); - - let dpr = self.window().hidpi_factor(); - let nspot_x = f64::from(px + point.col.0 as f32 * cw); - let nspot_y = f64::from(py + (point.line.0 + 1) as f32 * ch); - - self.window().set_ime_spot(PhysicalPosition::from((nspot_x, nspot_y)).to_logical(dpr)); - } -} diff --git a/src/event.rs b/src/event.rs deleted file mode 100644 index 1f3e9ca5..00000000 --- a/src/event.rs +++ /dev/null @@ -1,595 +0,0 @@ -//! Process window events -use std::borrow::Cow; -use std::env; -#[cfg(unix)] -use std::fs; -use std::fs::File; -use std::io::Write; -use std::sync::mpsc; -use std::time::Instant; - -use copypasta::{Buffer as ClipboardBuffer, Clipboard, Load, Store}; -use glutin::dpi::PhysicalSize; -use glutin::{self, ElementState, Event, ModifiersState, MouseButton}; -use parking_lot::MutexGuard; -use serde_json as json; - -use crate::cli::Options; -use crate::config::{self, Config}; -use crate::display::OnResize; -use crate::grid::Scroll; -use crate::index::{Column, Line, Point, Side}; -use crate::input::{self, KeyBinding, MouseBinding}; -use crate::selection::Selection; -use crate::sync::FairMutex; -use crate::term::cell::Cell; -use crate::term::{SizeInfo, Term}; -#[cfg(unix)] -use crate::tty; -use crate::util::fmt::Red; -use crate::util::{limit, start_daemon}; -use crate::window::Window; - -/// Byte sequences are sent to a `Notify` in response to some events -pub trait Notify { - /// Notify that an escape sequence should be written to the pty - /// - /// TODO this needs to be able to error somehow - fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, _: B); -} - -pub struct ActionContext<'a, N> { - pub notifier: &'a mut N, - pub terminal: &'a mut Term, - pub size_info: &'a mut SizeInfo, - pub mouse: &'a mut Mouse, - pub received_count: &'a mut usize, - pub suppress_chars: &'a mut bool, - pub last_modifiers: &'a mut ModifiersState, - pub window_changes: &'a mut WindowChanges, -} - -impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> { - fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, val: B) { - self.notifier.notify(val); - } - - fn size_info(&self) -> SizeInfo { - *self.size_info - } - - fn scroll(&mut self, scroll: Scroll) { - self.terminal.scroll_display(scroll); - - if let ElementState::Pressed = self.mouse().left_button_state { - let (x, y) = (self.mouse().x, self.mouse().y); - let size_info = self.size_info(); - let point = size_info.pixels_to_coords(x, y); - let cell_side = self.mouse().cell_side; - self.update_selection(Point { line: point.line, col: point.col }, cell_side); - } - } - - fn copy_selection(&self, buffer: ClipboardBuffer) { - if let Some(selected) = self.terminal.selection_to_string() { - if !selected.is_empty() { - Clipboard::new() - .and_then(|mut clipboard| clipboard.store(selected, buffer)) - .unwrap_or_else(|err| { - warn!("Error storing selection to clipboard. {}", Red(err)); - }); - } - } - } - - fn selection_is_empty(&self) -> bool { - self.terminal.selection().as_ref().map(Selection::is_empty).unwrap_or(true) - } - - fn clear_selection(&mut self) { - *self.terminal.selection_mut() = None; - self.terminal.dirty = true; - } - - fn update_selection(&mut self, point: Point, side: Side) { - let point = self.terminal.visible_to_buffer(point); - - // Update selection if one exists - if let Some(ref mut selection) = self.terminal.selection_mut() { - selection.update(point, side); - } - - self.terminal.dirty = true; - } - - fn simple_selection(&mut self, point: Point, side: Side) { - let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::simple(point, side)); - self.terminal.dirty = true; - } - - fn semantic_selection(&mut self, point: Point) { - let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::semantic(point)); - self.terminal.dirty = true; - } - - fn line_selection(&mut self, point: Point) { - let point = self.terminal.visible_to_buffer(point); - *self.terminal.selection_mut() = Some(Selection::lines(point)); - self.terminal.dirty = true; - } - - fn mouse_coords(&self) -> Option<Point> { - self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize) - } - - #[inline] - fn mouse_mut(&mut self) -> &mut Mouse { - self.mouse - } - - #[inline] - fn mouse(&self) -> &Mouse { - self.mouse - } - - #[inline] - fn received_count(&mut self) -> &mut usize { - &mut self.received_count - } - - #[inline] - fn suppress_chars(&mut self) -> &mut bool { - &mut self.suppress_chars - } - - #[inline] - fn last_modifiers(&mut self) -> &mut ModifiersState { - &mut self.last_modifiers - } - - #[inline] - fn hide_window(&mut self) { - self.window_changes.hide = true; - } - - #[inline] - fn terminal(&self) -> &Term { - self.terminal - } - - #[inline] - fn terminal_mut(&mut self) -> &mut Term { - self.terminal - } - - fn spawn_new_instance(&mut self) { - let alacritty = env::args().next().unwrap(); - - #[cfg(unix)] - let args = { - #[cfg(not(target_os = "freebsd"))] - let proc_prefix = ""; - #[cfg(target_os = "freebsd")] - let proc_prefix = "/compat/linux"; - let link_path = format!("{}/proc/{}/cwd", proc_prefix, tty::child_pid()); - if let Ok(path) = fs::read_link(link_path) { - vec!["--working-directory".into(), path] - } else { - Vec::new() - } - }; - #[cfg(not(unix))] - let args: Vec<String> = Vec::new(); - - match start_daemon(&alacritty, &args) { - Ok(_) => debug!("Started new Alacritty process: {} {:?}", alacritty, args), - Err(_) => warn!("Unable to start new Alacritty process: {} {:?}", alacritty, args), - } - } - - fn toggle_fullscreen(&mut self) { - self.window_changes.toggle_fullscreen(); - } - - #[cfg(target_os = "macos")] - fn toggle_simple_fullscreen(&mut self) { - self.window_changes.toggle_simple_fullscreen() - } -} - -/// The ActionContext can't really have direct access to the Window -/// with the current design. Event handlers that want to change the -/// window must set these flags instead. The processor will trigger -/// the actual changes. -#[derive(Default)] -pub struct WindowChanges { - pub hide: bool, - pub toggle_fullscreen: bool, - #[cfg(target_os = "macos")] - pub toggle_simple_fullscreen: bool, -} - -impl WindowChanges { - fn clear(&mut self) { - *self = WindowChanges::default(); - } - - fn toggle_fullscreen(&mut self) { - self.toggle_fullscreen = !self.toggle_fullscreen; - } - - #[cfg(target_os = "macos")] - fn toggle_simple_fullscreen(&mut self) { - self.toggle_simple_fullscreen = !self.toggle_simple_fullscreen; - } -} - -pub enum ClickState { - None, - Click, - DoubleClick, - TripleClick, -} - -/// State of the mouse -pub struct Mouse { - pub x: usize, - pub y: usize, - pub left_button_state: ElementState, - pub middle_button_state: ElementState, - pub right_button_state: ElementState, - pub last_click_timestamp: Instant, - pub click_state: ClickState, - pub scroll_px: i32, - pub line: Line, - pub column: Column, - pub cell_side: Side, - pub lines_scrolled: f32, - pub block_url_launcher: bool, - pub last_button: MouseButton, -} - -impl Default for Mouse { - fn default() -> Mouse { - Mouse { - x: 0, - y: 0, - last_click_timestamp: Instant::now(), - left_button_state: ElementState::Released, - middle_button_state: ElementState::Released, - right_button_state: ElementState::Released, - click_state: ClickState::None, - scroll_px: 0, - line: Line(0), - column: Column(0), - cell_side: Side::Left, - lines_scrolled: 0.0, - block_url_launcher: false, - last_button: MouseButton::Other(0), - } - } -} - -/// The event processor -/// -/// Stores some state from received events and dispatches actions when they are -/// triggered. -pub struct Processor<N> { - key_bindings: Vec<KeyBinding>, - mouse_bindings: Vec<MouseBinding>, - mouse_config: config::Mouse, - scrolling_config: config::Scrolling, - print_events: bool, - wait_for_event: bool, - notifier: N, - mouse: Mouse, - resize_tx: mpsc::Sender<PhysicalSize>, - ref_test: bool, - size_info: SizeInfo, - hide_mouse_when_typing: bool, - hide_mouse: bool, - received_count: usize, - suppress_chars: bool, - last_modifiers: ModifiersState, - pending_events: Vec<Event>, - window_changes: WindowChanges, - save_to_clipboard: bool, - alt_send_esc: bool, - is_fullscreen: bool, - is_simple_fullscreen: bool, -} - -/// Notify that the terminal was resized -/// -/// Currently this just forwards the notice to the input processor. -impl<N> OnResize for Processor<N> { - fn on_resize(&mut self, size: &SizeInfo) { - self.size_info = size.to_owned(); - } -} - -impl<N: Notify> Processor<N> { - /// Create a new event processor - /// - /// Takes a writer which is expected to be hooked up to the write end of a - /// pty. - pub fn new( - notifier: N, - resize_tx: mpsc::Sender<PhysicalSize>, - options: &Options, - config: &Config, - ref_test: bool, - size_info: SizeInfo, - ) -> Processor<N> { - Processor { - key_bindings: config.key_bindings().to_vec(), - mouse_bindings: config.mouse_bindings().to_vec(), - mouse_config: config.mouse().to_owned(), - scrolling_config: config.scrolling(), - print_events: options.print_events, - wait_for_event: true, - notifier, - resize_tx, - ref_test, - mouse: Default::default(), - size_info, - hide_mouse_when_typing: config.hide_mouse_when_typing(), - hide_mouse: false, - received_count: 0, - suppress_chars: false, - last_modifiers: Default::default(), - pending_events: Vec::with_capacity(4), - window_changes: Default::default(), - save_to_clipboard: config.selection().save_to_clipboard, - alt_send_esc: config.alt_send_esc(), - is_fullscreen: false, - is_simple_fullscreen: false, - } - } - - /// Handle events from glutin - /// - /// Doesn't take self mutably due to borrow checking. Kinda uggo but w/e. - fn handle_event<'a>( - processor: &mut input::Processor<'a, ActionContext<'a, N>>, - event: Event, - ref_test: bool, - resize_tx: &mpsc::Sender<PhysicalSize>, - hide_mouse: &mut bool, - window_is_focused: &mut bool, - ) { - match event { - // Pass on device events - Event::DeviceEvent { .. } | Event::Suspended { .. } => (), - Event::WindowEvent { event, .. } => { - use glutin::WindowEvent::*; - match event { - CloseRequested => { - if ref_test { - // dump grid state - let mut grid = processor.ctx.terminal.grid().clone(); - grid.initialize_all(&Cell::default()); - grid.truncate(); - - let serialized_grid = json::to_string(&grid).expect("serialize grid"); - - let serialized_size = - json::to_string(processor.ctx.terminal.size_info()) - .expect("serialize size"); - - let serialized_config = - format!("{{\"history_size\":{}}}", grid.history_size()); - - File::create("./grid.json") - .and_then(|mut f| f.write_all(serialized_grid.as_bytes())) - .expect("write grid.json"); - - File::create("./size.json") - .and_then(|mut f| f.write_all(serialized_size.as_bytes())) - .expect("write size.json"); - - File::create("./config.json") - .and_then(|mut f| f.write_all(serialized_config.as_bytes())) - .expect("write config.json"); - } - - processor.ctx.terminal.exit(); - }, - Resized(lsize) => { - // Resize events are emitted via glutin/winit with logical sizes - // However the terminal, window and renderer use physical sizes - // so a conversion must be done here - resize_tx - .send(lsize.to_physical(processor.ctx.size_info.dpr)) - .expect("send new size"); - processor.ctx.terminal.dirty = true; - }, - KeyboardInput { input, .. } => { - processor.process_key(input); - if input.state == ElementState::Pressed { - // Hide cursor while typing - *hide_mouse = true; - } - }, - ReceivedCharacter(c) => { - processor.received_char(c); - }, - MouseInput { state, button, modifiers, .. } => { - if !cfg!(target_os = "macos") || *window_is_focused { - *hide_mouse = false; - processor.mouse_input(state, button, modifiers); - processor.ctx.terminal.dirty = true; - } - }, - CursorMoved { position: lpos, modifiers, .. } => { - let (x, y) = lpos.to_physical(processor.ctx.size_info.dpr).into(); - let x: i32 = limit(x, 0, processor.ctx.size_info.width as i32); - let y: i32 = limit(y, 0, processor.ctx.size_info.height as i32); - - *hide_mouse = false; - processor.mouse_moved(x as usize, y as usize, modifiers); - }, - MouseWheel { delta, phase, modifiers, .. } => { - *hide_mouse = false; - processor.on_mouse_wheel(delta, phase, modifiers); - }, - Refresh => { - processor.ctx.terminal.dirty = true; - }, - Focused(is_focused) => { - *window_is_focused = is_focused; - - if is_focused { - processor.ctx.terminal.dirty = true; - processor.ctx.terminal.next_is_urgent = Some(false); - } else { - processor.ctx.terminal.reset_url_highlight(); - processor.ctx.terminal.dirty = true; - *hide_mouse = false; - } - - processor.on_focus_change(is_focused); - }, - DroppedFile(path) => { - use crate::input::ActionContext; - let path: String = path.to_string_lossy().into(); - processor.ctx.write_to_pty(path.into_bytes()); - }, - HiDpiFactorChanged(new_dpr) => { - processor.ctx.size_info.dpr = new_dpr; - processor.ctx.terminal.dirty = true; - }, - _ => (), - } - }, - Event::Awakened => { - processor.ctx.terminal.dirty = true; - }, - } - } - - /// Process events. When `wait_for_event` is set, this method is guaranteed - /// to process at least one event. - pub fn process_events<'a>( - &mut self, - term: &'a FairMutex<Term>, - window: &mut Window, - ) -> MutexGuard<'a, Term> { - // Terminal is lazily initialized the first time an event is returned - // from the blocking WaitEventsIterator. Otherwise, the pty reader would - // be blocked the entire time we wait for input! - let mut terminal; - - self.pending_events.clear(); - - { - // Ditto on lazy initialization for context and processor. - let context; - let mut processor: input::Processor<'_, ActionContext<'_, N>>; - - let print_events = self.print_events; - - let ref_test = self.ref_test; - let resize_tx = &self.resize_tx; - - if self.wait_for_event { - // A Vec is used here since wait_events can potentially yield - // multiple events before the interrupt is handled. For example, - // Resize and Moved events. - let pending_events = &mut self.pending_events; - window.wait_events(|e| { - pending_events.push(e); - glutin::ControlFlow::Break - }); - } - - terminal = term.lock(); - - context = ActionContext { - terminal: &mut terminal, - notifier: &mut self.notifier, - mouse: &mut self.mouse, - size_info: &mut self.size_info, - received_count: &mut self.received_count, - suppress_chars: &mut self.suppress_chars, - last_modifiers: &mut self.last_modifiers, - window_changes: &mut self.window_changes, - }; - - processor = input::Processor { - ctx: context, - scrolling_config: &self.scrolling_config, - mouse_config: &self.mouse_config, - key_bindings: &self.key_bindings[..], - mouse_bindings: &self.mouse_bindings[..], - save_to_clipboard: self.save_to_clipboard, - alt_send_esc: self.alt_send_esc, - }; - - let mut window_is_focused = window.is_focused; - - // Scope needed to that hide_mouse isn't borrowed after the scope - // ends. - { - let hide_mouse = &mut self.hide_mouse; - let mut process = |event| { - if print_events { - info!("glutin event: {:?}", event); - } - Processor::handle_event( - &mut processor, - event, - ref_test, - resize_tx, - hide_mouse, - &mut window_is_focused, - ); - }; - - for event in self.pending_events.drain(..) { - process(event); - } - - window.poll_events(process); - } - - if self.hide_mouse_when_typing { - window.set_mouse_visible(!self.hide_mouse); - } - - window.is_focused = window_is_focused; - } - - if self.window_changes.hide { - window.hide(); - } - - #[cfg(target_os = "macos")] - { - if self.window_changes.toggle_simple_fullscreen && !self.is_fullscreen { - window.set_simple_fullscreen(!self.is_simple_fullscreen); - self.is_simple_fullscreen = !self.is_simple_fullscreen; - } - } - - if self.window_changes.toggle_fullscreen && !self.is_simple_fullscreen { - window.set_fullscreen(!self.is_fullscreen); - self.is_fullscreen = !self.is_fullscreen; - } - - self.window_changes.clear(); - self.wait_for_event = !terminal.dirty; - - terminal - } - - pub fn update_config(&mut self, config: &Config) { - self.key_bindings = config.key_bindings().to_vec(); - self.mouse_bindings = config.mouse_bindings().to_vec(); - self.mouse_config = config.mouse().to_owned(); - self.save_to_clipboard = config.selection().save_to_clipboard; - self.alt_send_esc = config.alt_send_esc(); - } -} diff --git a/src/event_loop.rs b/src/event_loop.rs deleted file mode 100644 index 4941b479..00000000 --- a/src/event_loop.rs +++ /dev/null @@ -1,436 +0,0 @@ -//! The main event loop which performs I/O on the pseudoterminal -use std::borrow::Cow; -use std::collections::VecDeque; -use std::fs::File; -use std::io::{self, ErrorKind, Read, Write}; -use std::marker::Send; -use std::sync::Arc; - -use mio::{self, Events, PollOpt, Ready}; -use mio_extras::channel::{self, Receiver, Sender}; - -#[cfg(not(windows))] -use mio::unix::UnixReady; - -use crate::ansi; -use crate::display; -use crate::event; -use crate::sync::FairMutex; -use crate::term::Term; -use crate::tty; -use crate::util::thread; - -/// Messages that may be sent to the `EventLoop` -#[derive(Debug)] -pub enum Msg { - /// Data that should be written to the pty - Input(Cow<'static, [u8]>), - - /// Indicates that the `EventLoop` should shut down, as Alacritty is shutting down - Shutdown, -} - -/// The main event!.. loop. -/// -/// Handles all the pty I/O and runs the pty parser which updates terminal -/// state. -pub struct EventLoop<T: tty::EventedPty> { - poll: mio::Poll, - pty: T, - rx: Receiver<Msg>, - tx: Sender<Msg>, - terminal: Arc<FairMutex<Term>>, - display: display::Notifier, - ref_test: bool, -} - -/// Helper type which tracks how much of a buffer has been written. -struct Writing { - source: Cow<'static, [u8]>, - written: usize, -} - -/// Indicates the result of draining the mio channel -#[derive(Debug)] -enum DrainResult { - /// At least one new item was received - ReceivedItem, - /// Nothing was available to receive - Empty, - /// A shutdown message was received - Shutdown, -} - -impl DrainResult { - pub fn is_shutdown(&self) -> bool { - match *self { - DrainResult::Shutdown => true, - _ => false, - } - } -} - -/// All of the mutable state needed to run the event loop -/// -/// Contains list of items to write, current write state, etc. Anything that -/// would otherwise be mutated on the `EventLoop` goes here. -pub struct State { - write_list: VecDeque<Cow<'static, [u8]>>, - writing: Option<Writing>, - parser: ansi::Processor, -} - -pub struct Notifier(pub Sender<Msg>); - -impl event::Notify for Notifier { - fn notify<B>(&mut self, bytes: B) - where - B: Into<Cow<'static, [u8]>>, - { - let bytes = bytes.into(); - // terminal hangs if we send 0 bytes through. - if bytes.len() == 0 { - return; - } - if self.0.send(Msg::Input(bytes)).is_err() { - panic!("expected send event loop msg"); - } - } -} - -impl Default for State { - fn default() -> State { - State { write_list: VecDeque::new(), parser: ansi::Processor::new(), writing: None } - } -} - -impl State { - #[inline] - fn ensure_next(&mut self) { - if self.writing.is_none() { - self.goto_next(); - } - } - - #[inline] - fn goto_next(&mut self) { - self.writing = self.write_list.pop_front().map(Writing::new); - } - - #[inline] - fn take_current(&mut self) -> Option<Writing> { - self.writing.take() - } - - #[inline] - fn needs_write(&self) -> bool { - self.writing.is_some() || !self.write_list.is_empty() - } - - #[inline] - fn set_current(&mut self, new: Option<Writing>) { - self.writing = new; - } -} - -impl Writing { - #[inline] - fn new(c: Cow<'static, [u8]>) -> Writing { - Writing { source: c, written: 0 } - } - - #[inline] - fn advance(&mut self, n: usize) { - self.written += n; - } - - #[inline] - fn remaining_bytes(&self) -> &[u8] { - &self.source[self.written..] - } - - #[inline] - fn finished(&self) -> bool { - self.written >= self.source.len() - } -} - -impl<T> EventLoop<T> -where - T: tty::EventedPty + Send + 'static, -{ - /// Create a new event loop - pub fn new( - terminal: Arc<FairMutex<Term>>, - display: display::Notifier, - pty: T, - ref_test: bool, - ) -> EventLoop<T> { - let (tx, rx) = channel::channel(); - EventLoop { - poll: mio::Poll::new().expect("create mio Poll"), - pty, - tx, - rx, - terminal, - display, - ref_test, - } - } - - pub fn channel(&self) -> Sender<Msg> { - self.tx.clone() - } - - // Drain the channel - // - // Returns a `DrainResult` indicating the result of receiving from the channel - // - fn drain_recv_channel(&self, state: &mut State) -> DrainResult { - let mut received_item = false; - while let Ok(msg) = self.rx.try_recv() { - received_item = true; - match msg { - Msg::Input(input) => { - state.write_list.push_back(input); - }, - Msg::Shutdown => { - return DrainResult::Shutdown; - }, - } - } - - if received_item { - DrainResult::ReceivedItem - } else { - DrainResult::Empty - } - } - - // Returns a `bool` indicating whether or not the event loop should continue running - #[inline] - fn channel_event(&mut self, token: mio::Token, state: &mut State) -> bool { - if self.drain_recv_channel(state).is_shutdown() { - return false; - } - - self.poll - .reregister(&self.rx, token, Ready::readable(), PollOpt::edge() | PollOpt::oneshot()) - .unwrap(); - - true - } - - #[inline] - fn pty_read<X>( - &mut self, - state: &mut State, - buf: &mut [u8], - mut writer: Option<&mut X>, - ) -> io::Result<()> - where - X: Write, - { - const MAX_READ: usize = 0x1_0000; - let mut processed = 0; - let mut terminal = None; - - // Flag to keep track if wakeup has already been sent - let mut send_wakeup = false; - - loop { - match self.pty.reader().read(&mut buf[..]) { - Ok(0) => break, - Ok(got) => { - // Record bytes read; used to limit time spent in pty_read. - processed += got; - - // Send a copy of bytes read to a subscriber. Used for - // example with ref test recording. - writer = writer.map(|w| { - w.write_all(&buf[..got]).unwrap(); - w - }); - - // Get reference to terminal. Lock is acquired on initial - // iteration and held until there's no bytes left to parse - // or we've reached MAX_READ. - let terminal = if terminal.is_none() { - terminal = Some(self.terminal.lock()); - let terminal = terminal.as_mut().unwrap(); - send_wakeup = !terminal.dirty; - terminal - } else { - terminal.as_mut().unwrap() - }; - - // Run the parser - for byte in &buf[..got] { - state.parser.advance(&mut **terminal, *byte, &mut self.pty.writer()); - } - - // Exit if we've processed enough bytes - if processed > MAX_READ { - break; - } - }, - Err(err) => match err.kind() { - ErrorKind::Interrupted | ErrorKind::WouldBlock => { - break; - }, - _ => return Err(err), - }, - } - } - - // Only request a draw if one hasn't already been requested. - if let Some(mut terminal) = terminal { - if send_wakeup { - self.display.notify(); - terminal.dirty = true; - } - } - - Ok(()) - } - - #[inline] - fn pty_write(&mut self, state: &mut State) -> io::Result<()> { - state.ensure_next(); - - 'write_many: while let Some(mut current) = state.take_current() { - 'write_one: loop { - match self.pty.writer().write(current.remaining_bytes()) { - Ok(0) => { - state.set_current(Some(current)); - break 'write_many; - }, - Ok(n) => { - current.advance(n); - if current.finished() { - state.goto_next(); - break 'write_one; - } - }, - Err(err) => { - state.set_current(Some(current)); - match err.kind() { - ErrorKind::Interrupted | ErrorKind::WouldBlock => break 'write_many, - _ => return Err(err), - } - }, - } - } - } - - Ok(()) - } - - pub fn spawn(mut self, state: Option<State>) -> thread::JoinHandle<(Self, State)> { - thread::spawn_named("pty reader", move || { - let mut state = state.unwrap_or_else(Default::default); - let mut buf = [0u8; 0x1000]; - - let mut tokens = (0..).map(Into::into); - - let poll_opts = PollOpt::edge() | PollOpt::oneshot(); - - let channel_token = tokens.next().unwrap(); - self.poll.register(&self.rx, channel_token, Ready::readable(), poll_opts).unwrap(); - - // Register TTY through EventedRW interface - self.pty.register(&self.poll, &mut tokens, Ready::readable(), poll_opts).unwrap(); - - let mut events = Events::with_capacity(1024); - - let mut pipe = if self.ref_test { - Some(File::create("./alacritty.recording").expect("create alacritty recording")) - } else { - None - }; - - 'event_loop: loop { - if let Err(err) = self.poll.poll(&mut events, None) { - match err.kind() { - ErrorKind::Interrupted => continue, - _ => panic!("EventLoop polling error: {:?}", err), - } - } - - for event in events.iter() { - match event.token() { - token if token == channel_token => { - if !self.channel_event(channel_token, &mut state) { - break 'event_loop; - } - }, - - #[cfg(unix)] - token if token == self.pty.child_event_token() => { - if let Some(tty::ChildEvent::Exited) = self.pty.next_child_event() { - self.terminal.lock().exit(); - self.display.notify(); - break 'event_loop; - } - }, - - token - if token == self.pty.read_token() - || token == self.pty.write_token() => - { - #[cfg(unix)] - { - if UnixReady::from(event.readiness()).is_hup() { - // don't try to do I/O on a dead PTY - continue; - } - } - - if event.readiness().is_readable() { - if let Err(e) = self.pty_read(&mut state, &mut buf, pipe.as_mut()) { - #[cfg(target_os = "linux")] - { - // On Linux, a `read` on the master side of a PTY can fail - // with `EIO` if the client side hangs up. In that case, - // just loop back round for the inevitable `Exited` event. - // This sucks, but checking the process is either racy or - // blocking. - if e.kind() == ErrorKind::Other { - continue; - } - } - - error!("Error reading from PTY in event loop: {}", e); - break 'event_loop; - } - } - - if event.readiness().is_writable() { - if let Err(e) = self.pty_write(&mut state) { - error!("Error writing to PTY in event loop: {}", e); - break 'event_loop; - } - } - } - _ => (), - } - } - - // Register write interest if necessary - let mut interest = Ready::readable(); - if state.needs_write() { - interest.insert(Ready::writable()); - } - // Reregister with new interest - self.pty.reregister(&self.poll, interest, poll_opts).unwrap(); - } - - // The evented instances are not dropped here so deregister them explicitly - // TODO: Is this still necessary? - let _ = self.poll.deregister(&self.rx); - let _ = self.pty.deregister(&self.poll); - - (self, state) - }) - } -} diff --git a/src/grid/mod.rs b/src/grid/mod.rs deleted file mode 100644 index 3a6bacf8..00000000 --- a/src/grid/mod.rs +++ /dev/null @@ -1,886 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! A specialized 2d grid implementation optimized for use in a terminal. - -use std::cmp::{max, min, Ordering}; -use std::ops::{Deref, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo}; - -use crate::index::{self, Column, IndexRange, Line, Point}; -use crate::selection::Selection; - -mod row; -pub use self::row::Row; - -#[cfg(test)] -mod tests; - -mod storage; -use self::storage::Storage; - -const MIN_INIT_SIZE: usize = 1_000; - -/// Bidirection iterator -pub trait BidirectionalIterator: Iterator { - fn prev(&mut self) -> Option<Self::Item>; -} - -/// An item in the grid along with its Line and Column. -pub struct Indexed<T> { - pub inner: T, - pub line: Line, - pub column: Column, -} - -impl<T> Deref for Indexed<T> { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - &self.inner - } -} - -impl<T: PartialEq> ::std::cmp::PartialEq for Grid<T> { - fn eq(&self, other: &Self) -> bool { - // Compare struct fields and check result of grid comparison - self.raw.eq(&other.raw) - && self.cols.eq(&other.cols) - && self.lines.eq(&other.lines) - && self.display_offset.eq(&other.display_offset) - && self.scroll_limit.eq(&other.scroll_limit) - && self.selection.eq(&other.selection) - && self.url_highlight.eq(&other.url_highlight) - } -} - -pub trait GridCell { - fn is_empty(&self) -> bool; - fn is_wrap(&self) -> bool; - fn set_wrap(&mut self, wrap: bool); -} - -/// Represents the terminal display contents -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Grid<T> { - /// Lines in the grid. Each row holds a list of cells corresponding to the - /// columns in that row. - raw: Storage<T>, - - /// Number of columns - cols: index::Column, - - /// Number of lines. - /// - /// Invariant: lines is equivalent to raw.len() - lines: index::Line, - - /// Offset of displayed area - /// - /// If the displayed region isn't at the bottom of the screen, it stays - /// stationary while more text is emitted. The scrolling implementation - /// updates this offset accordingly. - #[serde(default)] - display_offset: usize, - - /// An limit on how far back it's possible to scroll - #[serde(default)] - scroll_limit: usize, - - /// Selected region - #[serde(skip)] - pub selection: Option<Selection>, - - #[serde(default)] - max_scroll_limit: usize, - - /// Range for URL hover highlights - #[serde(default)] - pub url_highlight: Option<RangeInclusive<index::Linear>>, -} - -#[derive(Copy, Clone)] -pub enum Scroll { - Lines(isize), - PageUp, - PageDown, - Top, - Bottom, -} - -#[derive(Copy, Clone)] -pub enum ViewportPosition { - Visible(Line), - Above, - Below, -} - -impl<T: GridCell + Copy + Clone> Grid<T> { - pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid<T> { - let raw = Storage::with_capacity(lines, Row::new(cols, &template)); - Grid { - raw, - cols, - lines, - display_offset: 0, - scroll_limit: 0, - selection: None, - max_scroll_limit: scrollback, - url_highlight: None, - } - } - - pub fn visible_to_buffer(&self, point: Point) -> Point<usize> { - Point { line: self.visible_line_to_buffer(point.line), col: point.col } - } - - pub fn buffer_line_to_visible(&self, line: usize) -> ViewportPosition { - let offset = line.saturating_sub(self.display_offset); - if line < self.display_offset { - ViewportPosition::Below - } else if offset >= *self.num_lines() { - ViewportPosition::Above - } else { - ViewportPosition::Visible(self.lines - offset - 1) - } - } - - pub fn visible_line_to_buffer(&self, line: Line) -> usize { - self.line_to_offset(line) + self.display_offset - } - - /// Update the size of the scrollback history - pub fn update_history(&mut self, history_size: usize, template: &T) { - self.raw.update_history(history_size, Row::new(self.cols, &template)); - self.max_scroll_limit = history_size; - self.scroll_limit = min(self.scroll_limit, history_size); - self.display_offset = min(self.display_offset, self.scroll_limit); - } - - pub fn scroll_display(&mut self, scroll: Scroll) { - match scroll { - Scroll::Lines(count) => { - self.display_offset = min( - max((self.display_offset as isize) + count, 0isize) as usize, - self.scroll_limit, - ); - }, - Scroll::PageUp => { - self.display_offset = min(self.display_offset + self.lines.0, self.scroll_limit); - }, - Scroll::PageDown => { - self.display_offset -= min(self.display_offset, self.lines.0); - }, - Scroll::Top => self.display_offset = self.scroll_limit, - Scroll::Bottom => self.display_offset = 0, - } - } - - pub fn resize( - &mut self, - lines: index::Line, - cols: index::Column, - cursor_pos: &mut Point, - template: &T, - ) { - // Check that there's actually work to do and return early if not - if lines == self.lines && cols == self.cols { - return; - } - - match self.lines.cmp(&lines) { - Ordering::Less => self.grow_lines(lines, template), - Ordering::Greater => self.shrink_lines(lines), - Ordering::Equal => (), - } - - match self.cols.cmp(&cols) { - Ordering::Less => self.grow_cols(cols, cursor_pos, template), - Ordering::Greater => self.shrink_cols(cols, template), - Ordering::Equal => (), - } - } - - fn increase_scroll_limit(&mut self, count: usize, template: &T) { - self.scroll_limit = min(self.scroll_limit + count, self.max_scroll_limit); - - // Initialize new lines when the history buffer is smaller than the scroll limit - let history_size = self.raw.len().saturating_sub(*self.lines); - if history_size < self.scroll_limit { - let new = min( - max(self.scroll_limit - history_size, MIN_INIT_SIZE), - self.max_scroll_limit - history_size, - ); - self.raw.initialize(new, Row::new(self.cols, template)); - } - } - - fn decrease_scroll_limit(&mut self, count: usize) { - self.scroll_limit = self.scroll_limit.saturating_sub(count); - } - - /// Add lines to the visible area - /// - /// Alacritty keeps the cursor at the bottom of the terminal as long as there - /// is scrollback available. Once scrollback is exhausted, new lines are - /// simply added to the bottom of the screen. - fn grow_lines(&mut self, new_line_count: index::Line, template: &T) { - let lines_added = new_line_count - self.lines; - - // Need to "resize" before updating buffer - self.raw.grow_visible_lines(new_line_count, Row::new(self.cols, template)); - self.lines = new_line_count; - - // Move existing lines up if there is no scrollback to fill new lines - if lines_added.0 > self.scroll_limit { - let scroll_lines = lines_added - self.scroll_limit; - self.scroll_up(&(Line(0)..new_line_count), scroll_lines, template); - } - - self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added); - self.display_offset = self.display_offset.saturating_sub(*lines_added); - } - - fn grow_cols(&mut self, cols: index::Column, cursor_pos: &mut Point, template: &T) { - // Truncate all buffered lines - self.raw.grow_hidden(cols, template); - - let max_lines = self.lines.0 + self.max_scroll_limit; - - // Iterate backwards with indices for mutation during iteration - let mut i = self.raw.len(); - while i > 0 { - i -= 1; - - // Grow the current line if there's wrapped content available - while i >= 1 - && self.raw[i].len() < cols.0 - && self.raw[i].last().map(GridCell::is_wrap) == Some(true) - { - // Remove wrap flag before appending additional cells - if let Some(cell) = self.raw[i].last_mut() { - cell.set_wrap(false); - } - - // Append as many cells from the next line as possible - let len = min(self.raw[i - 1].len(), cols.0 - self.raw[i].len()); - let mut cells = self.raw[i - 1].front_split_off(len); - self.raw[i].append(&mut cells); - - if self.raw[i - 1].is_empty() { - // Remove following line if all cells have been drained - self.raw.remove(i - 1); - - if self.raw.len() < self.lines.0 || self.scroll_limit == 0 { - // Add new line and move lines up if we can't pull from history - self.raw.insert(0, Row::new(cols, template), max_lines); - cursor_pos.line = Line(cursor_pos.line.saturating_sub(1)); - } else { - // Make sure viewport doesn't move if line is outside of the visible area - if i < self.display_offset { - self.display_offset = self.display_offset.saturating_sub(1); - } - - // Remove one line from scrollback, since we just moved it to the viewport - self.scroll_limit = self.scroll_limit.saturating_sub(1); - self.display_offset = min(self.display_offset, self.scroll_limit); - i -= 1; - } - } else if let Some(cell) = self.raw[i].last_mut() { - // Set wrap flag if next line still has cells - cell.set_wrap(true); - } - } - - // Fill remaining cells - if self.raw[i].len() < cols.0 { - self.raw[i].grow(cols, template); - } - } - - self.cols = cols; - } - - fn shrink_cols(&mut self, cols: index::Column, template: &T) { - // Truncate all buffered lines - self.raw.shrink_hidden(cols); - - let max_lines = self.lines.0 + self.max_scroll_limit; - - // Iterate backwards with indices for mutation during iteration - let mut i = self.raw.len(); - while i > 0 { - i -= 1; - - if let Some(mut new_row) = self.raw[i].shrink(cols) { - // Set line as wrapped if cells got removed - if let Some(cell) = self.raw[i].last_mut() { - cell.set_wrap(true); - } - - if Some(true) == new_row.last().map(|c| c.is_wrap() && i >= 1) - && new_row.len() < cols.0 - { - // Make sure previous wrap flag doesn't linger around - if let Some(cell) = new_row.last_mut() { - cell.set_wrap(false); - } - - // Add removed cells to start of next row - self.raw[i - 1].append_front(new_row); - } else { - // Make sure viewport doesn't move if line is outside of the visible area - if i < self.display_offset { - self.display_offset = min(self.display_offset + 1, self.max_scroll_limit); - } - - // Make sure new row is at least as long as new width - let occ = new_row.len(); - if occ < cols.0 { - new_row.append(&mut vec![*template; cols.0 - occ]); - } - let row = Row::from_vec(new_row, occ); - - // Add new row with all removed cells - self.raw.insert(i, row, max_lines); - - // Increase scrollback history - self.scroll_limit = min(self.scroll_limit + 1, self.max_scroll_limit); - - // Since inserted might exceed cols, we need to check the same line again - i += 1; - } - } - } - - self.cols = cols; - } - - /// Remove lines from the visible area - /// - /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the - /// bottom of the screen. This is achieved by pushing history "out the top" - /// of the terminal window. - /// - /// Alacritty takes the same approach. - fn shrink_lines(&mut self, target: index::Line) { - let prev = self.lines; - - self.selection = None; - self.url_highlight = None; - self.raw.rotate(*prev as isize - *target as isize); - self.raw.shrink_visible_lines(target); - self.lines = target; - } - - /// Convert a Line index (active region) to a buffer offset - /// - /// # Panics - /// - /// This method will panic if `Line` is larger than the grid dimensions - pub fn line_to_offset(&self, line: index::Line) -> usize { - assert!(line < self.num_lines()); - - *(self.num_lines() - line - 1) - } - - #[inline] - pub fn scroll_down( - &mut self, - region: &Range<index::Line>, - positions: index::Line, - template: &T, - ) { - // Whether or not there is a scrolling region active, as long as it - // starts at the top, we can do a full rotation which just involves - // changing the start index. - // - // To accomodate scroll regions, rows are reordered at the end. - if region.start == Line(0) { - // Rotate the entire line buffer. If there's a scrolling region - // active, the bottom lines are restored in the next step. - self.raw.rotate_up(*positions); - if let Some(ref mut selection) = self.selection { - selection.rotate(-(*positions as isize)); - } - self.url_highlight = None; - - self.decrease_scroll_limit(*positions); - - // Now, restore any scroll region lines - let lines = self.lines; - for i in IndexRange(region.end..lines) { - self.raw.swap_lines(i, i + positions); - } - - // Finally, reset recycled lines - for i in IndexRange(Line(0)..positions) { - self.raw[i].reset(&template); - } - } else { - // Subregion rotation - for line in IndexRange((region.start + positions)..region.end).rev() { - self.raw.swap_lines(line, line - positions); - } - - for line in IndexRange(region.start..(region.start + positions)) { - self.raw[line].reset(&template); - } - } - } - - /// scroll_up moves lines at the bottom towards the top - /// - /// This is the performance-sensitive part of scrolling. - pub fn scroll_up(&mut self, region: &Range<index::Line>, positions: index::Line, template: &T) { - if region.start == Line(0) { - // Update display offset when not pinned to active area - if self.display_offset != 0 { - self.display_offset = - min(self.display_offset + *positions, self.len() - self.num_lines().0); - } - - self.increase_scroll_limit(*positions, template); - - // Rotate the entire line buffer. If there's a scrolling region - // active, the bottom lines are restored in the next step. - self.raw.rotate(-(*positions as isize)); - if let Some(ref mut selection) = self.selection { - selection.rotate(*positions as isize); - } - self.url_highlight = None; - - // // This next loop swaps "fixed" lines outside of a scroll region - // // back into place after the rotation. The work is done in buffer- - // // space rather than terminal-space to avoid redundant - // // transformations. - let fixed_lines = *self.num_lines() - *region.end; - - for i in 0..fixed_lines { - self.raw.swap(i, i + *positions); - } - - // Finally, reset recycled lines - // - // Recycled lines are just above the end of the scrolling region. - for i in 0..*positions { - self.raw[i + fixed_lines].reset(&template); - } - } else { - // Subregion rotation - for line in IndexRange(region.start..(region.end - positions)) { - self.raw.swap_lines(line, line + positions); - } - - // Clear reused lines - for line in IndexRange((region.end - positions)..region.end) { - self.raw[line].reset(&template); - } - } - } - - // Completely reset the grid state - pub fn reset(&mut self, template: &T) { - // Explicitly purge all lines from history - let shrinkage = self.raw.len() - self.lines.0; - self.raw.shrink_lines(shrinkage); - self.clear_history(); - - // Reset all visible lines - for row in 0..self.raw.len() { - self.raw[row].reset(template); - } - - self.display_offset = 0; - self.selection = None; - self.url_highlight = None; - } -} - -#[allow(clippy::len_without_is_empty)] -impl<T> Grid<T> { - #[inline] - pub fn num_lines(&self) -> index::Line { - self.lines - } - - pub fn display_iter(&self) -> DisplayIter<'_, T> { - DisplayIter::new(self) - } - - #[inline] - pub fn num_cols(&self) -> index::Column { - self.cols - } - - pub fn clear_history(&mut self) { - self.scroll_limit = 0; - } - - #[inline] - pub fn scroll_limit(&self) -> usize { - self.scroll_limit - } - - /// Total number of lines in the buffer, this includes scrollback + visible lines - #[inline] - pub fn len(&self) -> usize { - self.raw.len() - } - - #[inline] - pub fn history_size(&self) -> usize { - self.raw.len().saturating_sub(*self.lines) - } - - /// This is used only for initializing after loading ref-tests - pub fn initialize_all(&mut self, template: &T) - where - T: Copy, - { - let history_size = self.raw.len().saturating_sub(*self.lines); - self.raw.initialize(self.max_scroll_limit - history_size, Row::new(self.cols, template)); - } - - /// This is used only for truncating before saving ref-tests - pub fn truncate(&mut self) { - self.raw.truncate(); - } - - pub fn iter_from(&self, point: Point<usize>) -> GridIterator<'_, T> { - GridIterator { grid: self, cur: point } - } - - #[inline] - pub fn contains(&self, point: &Point) -> bool { - self.lines > point.line && self.cols > point.col - } - - #[inline] - pub fn display_offset(&self) -> usize { - self.display_offset - } -} - -pub struct GridIterator<'a, T> { - /// Immutable grid reference - grid: &'a Grid<T>, - - /// Current position of the iterator within the grid. - pub cur: Point<usize>, -} - -impl<'a, T> Iterator for GridIterator<'a, T> { - type Item = &'a T; - - fn next(&mut self) -> Option<Self::Item> { - let last_col = self.grid.num_cols() - Column(1); - match self.cur { - Point { line, col } if line == 0 && col == last_col => None, - Point { col, .. } if (col == last_col) => { - self.cur.line -= 1; - self.cur.col = Column(0); - Some(&self.grid[self.cur.line][self.cur.col]) - }, - _ => { - self.cur.col += Column(1); - Some(&self.grid[self.cur.line][self.cur.col]) - }, - } - } -} - -impl<'a, T> BidirectionalIterator for GridIterator<'a, T> { - fn prev(&mut self) -> Option<Self::Item> { - let num_cols = self.grid.num_cols(); - - match self.cur { - Point { line, col: Column(0) } if line == self.grid.len() - 1 => None, - Point { col: Column(0), .. } => { - self.cur.line += 1; - self.cur.col = num_cols - Column(1); - Some(&self.grid[self.cur.line][self.cur.col]) - }, - _ => { - self.cur.col -= Column(1); - Some(&self.grid[self.cur.line][self.cur.col]) - }, - } - } -} - -/// Index active region by line -impl<T> Index<index::Line> for Grid<T> { - type Output = Row<T>; - - #[inline] - fn index(&self, index: index::Line) -> &Row<T> { - &self.raw[index] - } -} - -/// Index with buffer offset -impl<T> Index<usize> for Grid<T> { - type Output = Row<T>; - - #[inline] - fn index(&self, index: usize) -> &Row<T> { - &self.raw[index] - } -} - -impl<T> IndexMut<index::Line> for Grid<T> { - #[inline] - fn index_mut(&mut self, index: index::Line) -> &mut Row<T> { - &mut self.raw[index] - } -} - -impl<T> IndexMut<usize> for Grid<T> { - #[inline] - fn index_mut(&mut self, index: usize) -> &mut Row<T> { - &mut self.raw[index] - } -} - -impl<'point, T> Index<&'point Point> for Grid<T> { - type Output = T; - - #[inline] - fn index<'a>(&'a self, point: &Point) -> &'a T { - &self[point.line][point.col] - } -} - -impl<'point, T> IndexMut<&'point Point> for Grid<T> { - #[inline] - fn index_mut<'a, 'b>(&'a mut self, point: &'b Point) -> &'a mut T { - &mut self[point.line][point.col] - } -} - -// ------------------------------------------------------------------------------------------------- -// REGIONS -// ------------------------------------------------------------------------------------------------- - -/// A subset of lines in the grid -/// -/// May be constructed using Grid::region(..) -pub struct Region<'a, T> { - start: Line, - end: Line, - raw: &'a Storage<T>, -} - -/// A mutable subset of lines in the grid -/// -/// May be constructed using Grid::region_mut(..) -pub struct RegionMut<'a, T> { - start: Line, - end: Line, - raw: &'a mut Storage<T>, -} - -impl<'a, T> RegionMut<'a, T> { - /// Call the provided function for every item in this region - pub fn each<F: Fn(&mut T)>(self, func: F) { - for row in self { - for item in row { - func(item) - } - } - } -} - -pub trait IndexRegion<I, T> { - /// Get an immutable region of Self - fn region(&self, _: I) -> Region<'_, T>; - - /// Get a mutable region of Self - fn region_mut(&mut self, _: I) -> RegionMut<'_, T>; -} - -impl<T> IndexRegion<Range<Line>, T> for Grid<T> { - fn region(&self, index: Range<Line>) -> Region<'_, T> { - assert!(index.start < self.num_lines()); - assert!(index.end <= self.num_lines()); - assert!(index.start <= index.end); - Region { start: index.start, end: index.end, raw: &self.raw } - } - - fn region_mut(&mut self, index: Range<Line>) -> RegionMut<'_, T> { - assert!(index.start < self.num_lines()); - assert!(index.end <= self.num_lines()); - assert!(index.start <= index.end); - RegionMut { start: index.start, end: index.end, raw: &mut self.raw } - } -} - -impl<T> IndexRegion<RangeTo<Line>, T> for Grid<T> { - fn region(&self, index: RangeTo<Line>) -> Region<'_, T> { - assert!(index.end <= self.num_lines()); - Region { start: Line(0), end: index.end, raw: &self.raw } - } - - fn region_mut(&mut self, index: RangeTo<Line>) -> RegionMut<'_, T> { - assert!(index.end <= self.num_lines()); - RegionMut { start: Line(0), end: index.end, raw: &mut self.raw } - } -} - -impl<T> IndexRegion<RangeFrom<Line>, T> for Grid<T> { - fn region(&self, index: RangeFrom<Line>) -> Region<'_, T> { - assert!(index.start < self.num_lines()); - Region { start: index.start, end: self.num_lines(), raw: &self.raw } - } - - fn region_mut(&mut self, index: RangeFrom<Line>) -> RegionMut<'_, T> { - assert!(index.start < self.num_lines()); - RegionMut { start: index.start, end: self.num_lines(), raw: &mut self.raw } - } -} - -impl<T> IndexRegion<RangeFull, T> for Grid<T> { - fn region(&self, _: RangeFull) -> Region<'_, T> { - Region { start: Line(0), end: self.num_lines(), raw: &self.raw } - } - - fn region_mut(&mut self, _: RangeFull) -> RegionMut<'_, T> { - RegionMut { start: Line(0), end: self.num_lines(), raw: &mut self.raw } - } -} - -pub struct RegionIter<'a, T> { - end: Line, - cur: Line, - raw: &'a Storage<T>, -} - -pub struct RegionIterMut<'a, T> { - end: Line, - cur: Line, - raw: &'a mut Storage<T>, -} - -impl<'a, T> IntoIterator for Region<'a, T> { - type IntoIter = RegionIter<'a, T>; - type Item = &'a Row<T>; - - fn into_iter(self) -> Self::IntoIter { - RegionIter { end: self.end, cur: self.start, raw: self.raw } - } -} - -impl<'a, T> IntoIterator for RegionMut<'a, T> { - type IntoIter = RegionIterMut<'a, T>; - type Item = &'a mut Row<T>; - - fn into_iter(self) -> Self::IntoIter { - RegionIterMut { end: self.end, cur: self.start, raw: self.raw } - } -} - -impl<'a, T> Iterator for RegionIter<'a, T> { - type Item = &'a Row<T>; - - fn next(&mut self) -> Option<Self::Item> { - if self.cur < self.end { - let index = self.cur; - self.cur += 1; - Some(&self.raw[index]) - } else { - None - } - } -} - -impl<'a, T> Iterator for RegionIterMut<'a, T> { - type Item = &'a mut Row<T>; - - fn next(&mut self) -> Option<Self::Item> { - if self.cur < self.end { - let index = self.cur; - self.cur += 1; - unsafe { Some(&mut *(&mut self.raw[index] as *mut _)) } - } else { - None - } - } -} - -// ------------------------------------------------------------------------------------------------- -// DISPLAY ITERATOR -// ------------------------------------------------------------------------------------------------- - -/// Iterates over the visible area accounting for buffer transform -pub struct DisplayIter<'a, T> { - grid: &'a Grid<T>, - offset: usize, - limit: usize, - col: Column, - line: Line, -} - -impl<'a, T: 'a> DisplayIter<'a, T> { - pub fn new(grid: &'a Grid<T>) -> DisplayIter<'a, T> { - let offset = grid.display_offset + *grid.num_lines() - 1; - let limit = grid.display_offset; - let col = Column(0); - let line = Line(0); - - DisplayIter { grid, offset, col, limit, line } - } - - pub fn offset(&self) -> usize { - self.offset - } - - pub fn column(&self) -> Column { - self.col - } - - pub fn line(&self) -> Line { - self.line - } -} - -impl<'a, T: Copy + 'a> Iterator for DisplayIter<'a, T> { - type Item = Indexed<T>; - - #[inline] - fn next(&mut self) -> Option<Self::Item> { - // Return None if we've reached the end. - if self.offset == self.limit && self.grid.num_cols() == self.col { - return None; - } - - // Get the next item. - let item = Some(Indexed { - inner: self.grid.raw[self.offset][self.col], - line: self.line, - column: self.col, - }); - - // Update line/col to point to next item - self.col += 1; - if self.col == self.grid.num_cols() && self.offset != self.limit { - self.offset -= 1; - - self.col = Column(0); - self.line = Line(*self.grid.lines - 1 - (self.offset - self.limit)); - } - - item - } -} diff --git a/src/grid/row.rs b/src/grid/row.rs deleted file mode 100644 index 88a23871..00000000 --- a/src/grid/row.rs +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Defines the Row type which makes up lines in the grid - -use std::cmp::{max, min}; -use std::ops::{Index, IndexMut}; -use std::ops::{Range, RangeFrom, RangeFull, RangeTo, RangeToInclusive}; -use std::slice; - -use crate::grid::GridCell; -use crate::index::Column; - -/// A row in the grid -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct Row<T> { - inner: Vec<T>, - - /// occupied entries - /// - /// Semantically, this value can be understood as the **end** of an - /// Exclusive Range. Thus, - /// - /// - Zero means there are no occupied entries - /// - 1 means there is a value at index zero, but nowhere else - /// - `occ == inner.len` means every value is occupied - pub(crate) occ: usize, -} - -impl<T: PartialEq> PartialEq for Row<T> { - fn eq(&self, other: &Self) -> bool { - self.inner == other.inner - } -} - -impl<T: Copy> Row<T> { - pub fn new(columns: Column, template: &T) -> Row<T> { - Row { inner: vec![*template; *columns], occ: 0 } - } - - pub fn grow(&mut self, cols: Column, template: &T) { - if self.inner.len() >= cols.0 { - return; - } - - self.inner.append(&mut vec![*template; cols.0 - self.len()]); - } - - pub fn shrink(&mut self, cols: Column) -> Option<Vec<T>> - where - T: GridCell, - { - if self.inner.len() <= cols.0 { - return None; - } - - // Split off cells for a new row - let mut new_row = self.inner.split_off(cols.0); - let index = new_row.iter().rposition(|c| !c.is_empty()).map(|i| i + 1).unwrap_or(0); - new_row.truncate(index); - - self.occ = min(self.occ, *cols); - - if new_row.is_empty() { - None - } else { - Some(new_row) - } - } - - /// Resets contents to the contents of `other` - #[inline(never)] - pub fn reset(&mut self, other: &T) { - for item in &mut self.inner[..self.occ] { - *item = *other; - } - self.occ = 0; - } -} - -#[allow(clippy::len_without_is_empty)] -impl<T> Row<T> { - #[inline] - pub fn from_vec(vec: Vec<T>, occ: usize) -> Row<T> { - Row { inner: vec, occ } - } - - #[inline] - pub fn len(&self) -> usize { - self.inner.len() - } - - #[inline] - pub fn last(&self) -> Option<&T> { - self.inner.last() - } - - #[inline] - pub fn last_mut(&mut self) -> Option<&mut T> { - self.occ = self.inner.len(); - self.inner.last_mut() - } - - #[inline] - pub fn append(&mut self, vec: &mut Vec<T>) - where - T: GridCell, - { - self.inner.append(vec); - self.occ = self.inner.iter().rposition(|c| !c.is_empty()).map(|i| i + 1).unwrap_or(0); - } - - #[inline] - pub fn append_front(&mut self, mut vec: Vec<T>) { - self.occ += vec.len(); - vec.append(&mut self.inner); - self.inner = vec; - } - - #[inline] - pub fn is_empty(&self) -> bool - where - T: GridCell, - { - self.inner.iter().all(GridCell::is_empty) - } - - #[inline] - pub fn front_split_off(&mut self, at: usize) -> Vec<T> { - self.occ = self.occ.saturating_sub(at); - - let mut split = self.inner.split_off(at); - std::mem::swap(&mut split, &mut self.inner); - split - } -} - -impl<'a, T> IntoIterator for &'a mut Row<T> { - type IntoIter = slice::IterMut<'a, T>; - type Item = &'a mut T; - - #[inline] - fn into_iter(self) -> slice::IterMut<'a, T> { - self.occ = self.len(); - self.inner.iter_mut() - } -} - -impl<T> Index<Column> for Row<T> { - type Output = T; - - #[inline] - fn index(&self, index: Column) -> &T { - &self.inner[index.0] - } -} - -impl<T> IndexMut<Column> for Row<T> { - #[inline] - fn index_mut(&mut self, index: Column) -> &mut T { - self.occ = max(self.occ, *index + 1); - &mut self.inner[index.0] - } -} - -// ----------------------------------------------------------------------------- -// Index ranges of columns -// ----------------------------------------------------------------------------- - -impl<T> Index<Range<Column>> for Row<T> { - type Output = [T]; - - #[inline] - fn index(&self, index: Range<Column>) -> &[T] { - &self.inner[(index.start.0)..(index.end.0)] - } -} - -impl<T> IndexMut<Range<Column>> for Row<T> { - #[inline] - fn index_mut(&mut self, index: Range<Column>) -> &mut [T] { - self.occ = max(self.occ, *index.end); - &mut self.inner[(index.start.0)..(index.end.0)] - } -} - -impl<T> Index<RangeTo<Column>> for Row<T> { - type Output = [T]; - - #[inline] - fn index(&self, index: RangeTo<Column>) -> &[T] { - &self.inner[..(index.end.0)] - } -} - -impl<T> IndexMut<RangeTo<Column>> for Row<T> { - #[inline] - fn index_mut(&mut self, index: RangeTo<Column>) -> &mut [T] { - self.occ = max(self.occ, *index.end); - &mut self.inner[..(index.end.0)] - } -} - -impl<T> Index<RangeFrom<Column>> for Row<T> { - type Output = [T]; - - #[inline] - fn index(&self, index: RangeFrom<Column>) -> &[T] { - &self.inner[(index.start.0)..] - } -} - -impl<T> IndexMut<RangeFrom<Column>> for Row<T> { - #[inline] - fn index_mut(&mut self, index: RangeFrom<Column>) -> &mut [T] { - self.occ = self.len(); - &mut self.inner[(index.start.0)..] - } -} - -impl<T> Index<RangeFull> for Row<T> { - type Output = [T]; - - #[inline] - fn index(&self, _: RangeFull) -> &[T] { - &self.inner[..] - } -} - -impl<T> IndexMut<RangeFull> for Row<T> { - #[inline] - fn index_mut(&mut self, _: RangeFull) -> &mut [T] { - self.occ = self.len(); - &mut self.inner[..] - } -} - -impl<T> Index<RangeToInclusive<Column>> for Row<T> { - type Output = [T]; - - #[inline] - fn index(&self, index: RangeToInclusive<Column>) -> &[T] { - &self.inner[..=(index.end.0)] - } -} - -impl<T> IndexMut<RangeToInclusive<Column>> for Row<T> { - #[inline] - fn index_mut(&mut self, index: RangeToInclusive<Column>) -> &mut [T] { - self.occ = max(self.occ, *index.end); - &mut self.inner[..=(index.end.0)] - } -} diff --git a/src/grid/storage.rs b/src/grid/storage.rs deleted file mode 100644 index 32260426..00000000 --- a/src/grid/storage.rs +++ /dev/null @@ -1,922 +0,0 @@ -/// Wrapper around Vec which supports fast indexing and rotation -/// -/// The rotation implemented by grid::Storage is a simple integer addition. -/// Compare with standard library rotation which requires rearranging items in -/// memory. -/// -/// As a consequence, the indexing operators need to be reimplemented for this -/// type to account for the 0th element not always being at the start of the -/// allocation. -/// -/// Because certain Vec operations are no longer valid on this type, no Deref -/// implementation is provided. Anything from Vec that should be exposed must be -/// done so manually. -use std::ops::{Index, IndexMut}; - -use static_assertions::assert_eq_size; - -use super::Row; -use crate::grid::GridCell; -use crate::index::{Column, Line}; - -/// Maximum number of invisible lines before buffer is resized -const TRUNCATE_STEP: usize = 100; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Storage<T> { - inner: Vec<Row<T>>, - zero: usize, - visible_lines: Line, - - /// Total number of lines currently active in the terminal (scrollback + visible) - /// - /// Shrinking this length allows reducing the number of lines in the scrollback buffer without - /// having to truncate the raw `inner` buffer. - /// As long as `len` is bigger than `inner`, it is also possible to grow the scrollback buffer - /// without any additional insertions. - #[serde(default)] - len: usize, -} - -impl<T: PartialEq> ::std::cmp::PartialEq for Storage<T> { - fn eq(&self, other: &Self) -> bool { - // Make sure length is equal - if self.inner.len() != other.inner.len() { - return false; - } - - // Check which vec has the bigger zero - let (ref bigger, ref smaller) = - if self.zero >= other.zero { (self, other) } else { (other, self) }; - - // Calculate the actual zero offset - let len = self.inner.len(); - let bigger_zero = bigger.zero % len; - let smaller_zero = smaller.zero % len; - - // Compare the slices in chunks - // Chunks: - // - Bigger zero to the end - // - Remaining lines in smaller zero vec - // - Beginning of smaller zero vec - // - // Example: - // Bigger Zero (6): - // 4 5 6 | 7 8 9 | 0 1 2 3 - // C2 C2 C2 | C3 C3 C3 | C1 C1 C1 C1 - // Smaller Zero (3): - // 7 8 9 | 0 1 2 3 | 4 5 6 - // C3 C3 C3 | C1 C1 C1 C1 | C2 C2 C2 - bigger.inner[bigger_zero..] - == smaller.inner[smaller_zero..smaller_zero + (len - bigger_zero)] - && bigger.inner[..bigger_zero - smaller_zero] - == smaller.inner[smaller_zero + (len - bigger_zero)..] - && bigger.inner[bigger_zero - smaller_zero..bigger_zero] - == smaller.inner[..smaller_zero] - } -} - -impl<T> Storage<T> { - #[inline] - pub fn with_capacity(lines: Line, template: Row<T>) -> Storage<T> - where - T: Clone, - { - // Initialize visible lines, the scrollback buffer is initialized dynamically - let inner = vec![template; lines.0]; - - Storage { inner, zero: 0, visible_lines: lines - 1, len: lines.0 } - } - - /// Update the size of the scrollback history - pub fn update_history(&mut self, history_size: usize, template_row: Row<T>) - where - T: Clone, - { - let current_history = self.len - (self.visible_lines.0 + 1); - if history_size > current_history { - self.grow_lines(history_size - current_history, template_row); - } else if history_size < current_history { - self.shrink_lines(current_history - history_size); - } - } - - /// Increase the number of lines in the buffer - pub fn grow_visible_lines(&mut self, next: Line, template_row: Row<T>) - where - T: Clone, - { - // Number of lines the buffer needs to grow - let growage = (next - (self.visible_lines + 1)).0; - self.grow_lines(growage, template_row); - - // Update visible lines - self.visible_lines = next - 1; - } - - /// Grow the number of lines in the buffer, filling new lines with the template - fn grow_lines(&mut self, growage: usize, template_row: Row<T>) - where - T: Clone, - { - // Only grow if there are not enough lines still hidden - let mut new_growage = 0; - if growage > (self.inner.len() - self.len) { - // Lines to grow additionally to invisible lines - new_growage = growage - (self.inner.len() - self.len); - - // Split off the beginning of the raw inner buffer - let mut start_buffer = self.inner.split_off(self.zero); - - // Insert new template rows at the end of the raw inner buffer - let mut new_lines = vec![template_row; new_growage]; - self.inner.append(&mut new_lines); - - // Add the start to the raw inner buffer again - self.inner.append(&mut start_buffer); - } - - // Update raw buffer length and zero offset - self.zero = (self.zero + new_growage) % self.inner.len(); - self.len += growage; - } - - /// Decrease the number of lines in the buffer - pub fn shrink_visible_lines(&mut self, next: Line) { - // Shrink the size without removing any lines - let shrinkage = (self.visible_lines - (next - 1)).0; - self.shrink_lines(shrinkage); - - // Update visible lines - self.visible_lines = next - 1; - } - - // Shrink the number of lines in the buffer - pub fn shrink_lines(&mut self, shrinkage: usize) { - self.len -= shrinkage; - - // Free memory - if self.inner.len() > self.len() + TRUNCATE_STEP { - self.truncate(); - } - } - - /// Truncate the invisible elements from the raw buffer - pub fn truncate(&mut self) { - self.inner.rotate_left(self.zero); - self.inner.truncate(self.len); - - self.zero = 0; - } - - /// Dynamically grow the storage buffer at runtime - pub fn initialize(&mut self, num_rows: usize, template_row: Row<T>) - where - T: Clone, - { - let mut new = vec![template_row; num_rows]; - - let mut split = self.inner.split_off(self.zero); - self.inner.append(&mut new); - self.inner.append(&mut split); - - self.zero += num_rows; - self.len += num_rows; - } - - #[inline] - pub fn len(&self) -> usize { - self.len - } - - #[inline] - /// Compute actual index in underlying storage given the requested index. - fn compute_index(&self, requested: usize) -> usize { - debug_assert!(requested < self.len); - let zeroed = requested + self.zero; - - // This part is critical for performance, - // so an if/else is used here instead of a moludo operation - if zeroed >= self.inner.len() { - zeroed - self.inner.len() - } else { - zeroed - } - } - - pub fn swap_lines(&mut self, a: Line, b: Line) { - let offset = self.inner.len() + self.zero + *self.visible_lines; - let a = (offset - *a) % self.inner.len(); - let b = (offset - *b) % self.inner.len(); - self.inner.swap(a, b); - } - - /// Swap implementation for Row<T>. - /// - /// Exploits the known size of Row<T> to produce a slightly more efficient - /// swap than going through slice::swap. - /// - /// The default implementation from swap generates 8 movups and 4 movaps - /// instructions. This implementation achieves the swap in only 8 movups - /// instructions. - pub fn swap(&mut self, a: usize, b: usize) { - assert_eq_size!(Row<T>, [usize; 4]); - - let a = self.compute_index(a); - let b = self.compute_index(b); - - unsafe { - // Cast to a qword array to opt out of copy restrictions and avoid - // drop hazards. Byte array is no good here since for whatever - // reason LLVM won't optimized it. - let a_ptr = self.inner.as_mut_ptr().add(a) as *mut usize; - let b_ptr = self.inner.as_mut_ptr().add(b) as *mut usize; - - // Copy 1 qword at a time - // - // The optimizer unrolls this loop and vectorizes it. - let mut tmp: usize; - for i in 0..4 { - tmp = *a_ptr.offset(i); - *a_ptr.offset(i) = *b_ptr.offset(i); - *b_ptr.offset(i) = tmp; - } - } - } - - #[inline] - pub fn rotate(&mut self, count: isize) { - debug_assert!(count.abs() as usize <= self.inner.len()); - - let len = self.inner.len(); - self.zero = (self.zero as isize + count + len as isize) as usize % len; - } - - // Fast path - #[inline] - pub fn rotate_up(&mut self, count: usize) { - self.zero = (self.zero + count) % self.inner.len(); - } - - #[inline] - pub fn insert(&mut self, index: usize, row: Row<T>, max_lines: usize) { - let index = self.compute_index(index); - self.inner.insert(index, row); - - if index < self.zero { - self.zero += 1; - } - - if self.len < max_lines { - self.len += 1; - } - } - - #[inline] - pub fn remove(&mut self, index: usize) -> Row<T> { - let index = self.compute_index(index); - if index < self.zero { - self.zero -= 1; - } - self.len -= 1; - - self.inner.remove(index) - } - - /// Shrink columns of hidden buffered lines. - /// - /// XXX This suggests that Storage is a leaky abstraction. Ultimately, this - /// is needed because of the grow/shrink lines functionality. - #[inline] - pub fn shrink_hidden(&mut self, cols: Column) - where - T: GridCell + Copy, - { - let start = self.zero + self.len; - let end = self.zero + self.inner.len(); - for mut i in start..end { - if i >= self.inner.len() { - i -= self.inner.len(); - } - - self.inner[i].shrink(cols); - } - } - - /// Grow columns of hidden buffered lines. - /// - /// XXX This suggests that Storage is a leaky abstraction. Ultimately, this - /// is needed because of the grow/shrink lines functionality. - #[inline] - pub fn grow_hidden(&mut self, cols: Column, template: &T) - where - T: Copy + Clone, - { - let start = self.zero + self.len; - let end = self.zero + self.inner.len(); - for mut i in start..end { - if i >= self.inner.len() { - i -= self.inner.len(); - } - - self.inner[i].grow(cols, template); - } - } -} - -impl<T> Index<usize> for Storage<T> { - type Output = Row<T>; - - #[inline] - fn index(&self, index: usize) -> &Self::Output { - &self.inner[self.compute_index(index)] - } -} - -impl<T> IndexMut<usize> for Storage<T> { - #[inline] - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - let index = self.compute_index(index); // borrowck - &mut self.inner[index] - } -} - -impl<T> Index<Line> for Storage<T> { - type Output = Row<T>; - - #[inline] - fn index(&self, index: Line) -> &Self::Output { - let index = self.visible_lines - index; - &self[*index] - } -} - -impl<T> IndexMut<Line> for Storage<T> { - #[inline] - fn index_mut(&mut self, index: Line) -> &mut Self::Output { - let index = self.visible_lines - index; - &mut self[*index] - } -} - -/// Grow the buffer one line at the end of the buffer -/// -/// Before: -/// 0: 0 <- Zero -/// 1: 1 -/// 2: - -/// After: -/// 0: - -/// 1: 0 <- Zero -/// 2: 1 -/// 3: - -#[test] -fn grow_after_zero() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'-'), - ], - zero: 0, - visible_lines: Line(2), - len: 3, - }; - - // Grow buffer - storage.grow_visible_lines(Line(4), Row::new(Column(1), &'-')); - - // Make sure the result is correct - let expected = Storage { - inner: vec![ - Row::new(Column(1), &'-'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'-'), - ], - zero: 1, - visible_lines: Line(0), - len: 4, - }; - assert_eq!(storage.inner, expected.inner); - assert_eq!(storage.zero, expected.zero); - assert_eq!(storage.len, expected.len); -} - -/// Grow the buffer one line at the start of the buffer -/// -/// Before: -/// 0: - -/// 1: 0 <- Zero -/// 2: 1 -/// After: -/// 0: - -/// 1: - -/// 2: 0 <- Zero -/// 3: 1 -#[test] -fn grow_before_zero() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'-'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - ], - zero: 1, - visible_lines: Line(2), - len: 3, - }; - - // Grow buffer - storage.grow_visible_lines(Line(4), Row::new(Column(1), &'-')); - - // Make sure the result is correct - let expected = Storage { - inner: vec![ - Row::new(Column(1), &'-'), - Row::new(Column(1), &'-'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - ], - zero: 2, - visible_lines: Line(0), - len: 4, - }; - assert_eq!(storage.inner, expected.inner); - assert_eq!(storage.zero, expected.zero); - assert_eq!(storage.len, expected.len); -} - -/// Shrink the buffer one line at the start of the buffer -/// -/// Before: -/// 0: 2 -/// 1: 0 <- Zero -/// 2: 1 -/// After: -/// 0: 2 <- Hidden -/// 0: 0 <- Zero -/// 1: 1 -#[test] -fn shrink_before_zero() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'2'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - ], - zero: 1, - visible_lines: Line(2), - len: 3, - }; - - // Shrink buffer - storage.shrink_visible_lines(Line(2)); - - // Make sure the result is correct - let expected = Storage { - inner: vec![ - Row::new(Column(1), &'2'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - ], - zero: 1, - visible_lines: Line(0), - len: 2, - }; - assert_eq!(storage.inner, expected.inner); - assert_eq!(storage.zero, expected.zero); - assert_eq!(storage.len, expected.len); -} - -/// Shrink the buffer one line at the end of the buffer -/// -/// Before: -/// 0: 0 <- Zero -/// 1: 1 -/// 2: 2 -/// After: -/// 0: 0 <- Zero -/// 1: 1 -/// 2: 2 <- Hidden -#[test] -fn shrink_after_zero() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - ], - zero: 0, - visible_lines: Line(2), - len: 3, - }; - - // Shrink buffer - storage.shrink_visible_lines(Line(2)); - - // Make sure the result is correct - let expected = Storage { - inner: vec![ - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - ], - zero: 0, - visible_lines: Line(0), - len: 2, - }; - assert_eq!(storage.inner, expected.inner); - assert_eq!(storage.zero, expected.zero); - assert_eq!(storage.len, expected.len); -} - -/// Shrink the buffer at the start and end of the buffer -/// -/// Before: -/// 0: 4 -/// 1: 5 -/// 2: 0 <- Zero -/// 3: 1 -/// 4: 2 -/// 5: 3 -/// After: -/// 0: 4 <- Hidden -/// 1: 5 <- Hidden -/// 2: 0 <- Zero -/// 3: 1 -/// 4: 2 <- Hidden -/// 5: 3 <- Hidden -#[test] -fn shrink_before_and_after_zero() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(5), - len: 6, - }; - - // Shrink buffer - storage.shrink_visible_lines(Line(2)); - - // Make sure the result is correct - let expected = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(0), - len: 2, - }; - assert_eq!(storage.inner, expected.inner); - assert_eq!(storage.zero, expected.zero); - assert_eq!(storage.len, expected.len); -} - -/// Check that when truncating all hidden lines are removed from the raw buffer -/// -/// Before: -/// 0: 4 <- Hidden -/// 1: 5 <- Hidden -/// 2: 0 <- Zero -/// 3: 1 -/// 4: 2 <- Hidden -/// 5: 3 <- Hidden -/// After: -/// 0: 0 <- Zero -/// 1: 1 -#[test] -fn truncate_invisible_lines() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(1), - len: 2, - }; - - // Truncate buffer - storage.truncate(); - - // Make sure the result is correct - let expected = Storage { - inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], - zero: 0, - visible_lines: Line(1), - len: 2, - }; - assert_eq!(storage.visible_lines, expected.visible_lines); - assert_eq!(storage.inner, expected.inner); - assert_eq!(storage.zero, expected.zero); - assert_eq!(storage.len, expected.len); -} - -/// Truncate buffer only at the beginning -/// -/// Before: -/// 0: 1 -/// 1: 2 <- Hidden -/// 2: 0 <- Zero -/// After: -/// 0: 1 -/// 0: 0 <- Zero -#[test] -fn truncate_invisible_lines_beginning() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'0'), - ], - zero: 2, - visible_lines: Line(1), - len: 2, - }; - - // Truncate buffer - storage.truncate(); - - // Make sure the result is correct - let expected = Storage { - inner: vec![Row::new(Column(1), &'0'), Row::new(Column(1), &'1')], - zero: 0, - visible_lines: Line(1), - len: 2, - }; - assert_eq!(storage.visible_lines, expected.visible_lines); - assert_eq!(storage.inner, expected.inner); - assert_eq!(storage.zero, expected.zero); - assert_eq!(storage.len, expected.len); -} - -/// First shrink the buffer and then grow it again -/// -/// Before: -/// 0: 4 -/// 1: 5 -/// 2: 0 <- Zero -/// 3: 1 -/// 4: 2 -/// 5: 3 -/// After Shrinking: -/// 0: 4 <- Hidden -/// 1: 5 <- Hidden -/// 2: 0 <- Zero -/// 3: 1 -/// 4: 2 -/// 5: 3 <- Hidden -/// After Growing: -/// 0: 4 -/// 1: 5 -/// 2: - -/// 3: 0 <- Zero -/// 4: 1 -/// 5: 2 -/// 6: 3 -#[test] -fn shrink_then_grow() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(0), - len: 6, - }; - - // Shrink buffer - storage.shrink_lines(3); - - // Make sure the result after shrinking is correct - let shrinking_expected = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(0), - len: 3, - }; - assert_eq!(storage.inner, shrinking_expected.inner); - assert_eq!(storage.zero, shrinking_expected.zero); - assert_eq!(storage.len, shrinking_expected.len); - - // Grow buffer - storage.grow_lines(4, Row::new(Column(1), &'-')); - - // Make sure the result after shrinking is correct - let growing_expected = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'-'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 3, - visible_lines: Line(0), - len: 7, - }; - assert_eq!(storage.inner, growing_expected.inner); - assert_eq!(storage.zero, growing_expected.zero); - assert_eq!(storage.len, growing_expected.len); -} - -#[test] -fn initialize() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(0), - len: 6, - }; - - // Initialize additional lines - storage.initialize(3, Row::new(Column(1), &'-')); - - // Make sure the lines are present and at the right location - let shrinking_expected = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'-'), - Row::new(Column(1), &'-'), - Row::new(Column(1), &'-'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 5, - visible_lines: Line(0), - len: 9, - }; - assert_eq!(storage.inner, shrinking_expected.inner); - assert_eq!(storage.zero, shrinking_expected.zero); - assert_eq!(storage.len, shrinking_expected.len); -} - -#[test] -fn insert() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(0), - len: 6, - }; - - // Initialize additional lines - storage.insert(2, Row::new(Column(1), &'-'), 100); - - // Make sure the lines are present and at the right location - let shrinking_expected = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'-'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(0), - len: 7, - }; - assert_eq!(storage.inner, shrinking_expected.inner); - assert_eq!(storage.zero, shrinking_expected.zero); - assert_eq!(storage.len, shrinking_expected.len); -} - -#[test] -fn insert_truncate_max() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(0), - len: 6, - }; - - // Initialize additional lines - storage.insert(2, Row::new(Column(1), &'-'), 6); - - // Make sure the lines are present and at the right location - let shrinking_expected = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'-'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(0), - len: 6, - }; - assert_eq!(storage.inner, shrinking_expected.inner); - assert_eq!(storage.zero, shrinking_expected.zero); - assert_eq!(storage.len, shrinking_expected.len); -} - -#[test] -fn insert_at_zero() { - // Setup storage area - let mut storage = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(0), - len: 6, - }; - - // Initialize additional lines - storage.insert(0, Row::new(Column(1), &'-'), 6); - - // Make sure the lines are present and at the right location - let shrinking_expected = Storage { - inner: vec![ - Row::new(Column(1), &'4'), - Row::new(Column(1), &'5'), - Row::new(Column(1), &'-'), - Row::new(Column(1), &'0'), - Row::new(Column(1), &'1'), - Row::new(Column(1), &'2'), - Row::new(Column(1), &'3'), - ], - zero: 2, - visible_lines: Line(0), - len: 6, - }; - assert_eq!(storage.inner, shrinking_expected.inner); - assert_eq!(storage.zero, shrinking_expected.zero); - assert_eq!(storage.len, shrinking_expected.len); -} diff --git a/src/grid/tests.rs b/src/grid/tests.rs deleted file mode 100644 index fc41fdc6..00000000 --- a/src/grid/tests.rs +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Tests for the Gird - -use super::{BidirectionalIterator, Grid}; -use crate::grid::GridCell; -use crate::index::{Column, Line, Point}; -use crate::term::cell::{Cell, Flags}; - -impl GridCell for usize { - fn is_empty(&self) -> bool { - false - } - - fn is_wrap(&self) -> bool { - false - } - - fn set_wrap(&mut self, _wrap: bool) {} -} - -// Scroll up moves lines upwards -#[test] -fn scroll_up() { - let mut grid = Grid::new(Line(10), Column(1), 0, 0); - for i in 0..10 { - grid[Line(i)][Column(0)] = i; - } - - grid.scroll_up(&(Line(0)..Line(10)), Line(2), &0); - - assert_eq!(grid[Line(0)][Column(0)], 2); - assert_eq!(grid[Line(0)].occ, 1); - assert_eq!(grid[Line(1)][Column(0)], 3); - assert_eq!(grid[Line(1)].occ, 1); - assert_eq!(grid[Line(2)][Column(0)], 4); - assert_eq!(grid[Line(2)].occ, 1); - assert_eq!(grid[Line(3)][Column(0)], 5); - assert_eq!(grid[Line(3)].occ, 1); - assert_eq!(grid[Line(4)][Column(0)], 6); - assert_eq!(grid[Line(4)].occ, 1); - assert_eq!(grid[Line(5)][Column(0)], 7); - assert_eq!(grid[Line(5)].occ, 1); - assert_eq!(grid[Line(6)][Column(0)], 8); - assert_eq!(grid[Line(6)].occ, 1); - assert_eq!(grid[Line(7)][Column(0)], 9); - assert_eq!(grid[Line(7)].occ, 1); - assert_eq!(grid[Line(8)][Column(0)], 0); // was 0 - assert_eq!(grid[Line(8)].occ, 0); - assert_eq!(grid[Line(9)][Column(0)], 0); // was 1 - assert_eq!(grid[Line(9)].occ, 0); -} - -// Scroll down moves lines downwards -#[test] -fn scroll_down() { - let mut grid = Grid::new(Line(10), Column(1), 0, 0); - for i in 0..10 { - grid[Line(i)][Column(0)] = i; - } - - grid.scroll_down(&(Line(0)..Line(10)), Line(2), &0); - - assert_eq!(grid[Line(0)][Column(0)], 0); // was 8 - assert_eq!(grid[Line(0)].occ, 0); - assert_eq!(grid[Line(1)][Column(0)], 0); // was 9 - assert_eq!(grid[Line(1)].occ, 0); - assert_eq!(grid[Line(2)][Column(0)], 0); - assert_eq!(grid[Line(2)].occ, 1); - assert_eq!(grid[Line(3)][Column(0)], 1); - assert_eq!(grid[Line(3)].occ, 1); - assert_eq!(grid[Line(4)][Column(0)], 2); - assert_eq!(grid[Line(4)].occ, 1); - assert_eq!(grid[Line(5)][Column(0)], 3); - assert_eq!(grid[Line(5)].occ, 1); - assert_eq!(grid[Line(6)][Column(0)], 4); - assert_eq!(grid[Line(6)].occ, 1); - assert_eq!(grid[Line(7)][Column(0)], 5); - assert_eq!(grid[Line(7)].occ, 1); - assert_eq!(grid[Line(8)][Column(0)], 6); - assert_eq!(grid[Line(8)].occ, 1); - assert_eq!(grid[Line(9)][Column(0)], 7); - assert_eq!(grid[Line(9)].occ, 1); -} - -// Test that GridIterator works -#[test] -fn test_iter() { - let mut grid = Grid::new(Line(5), Column(5), 0, 0); - for i in 0..5 { - for j in 0..5 { - grid[Line(i)][Column(j)] = i * 5 + j; - } - } - - let mut iter = grid.iter_from(Point { line: 4, col: Column(0) }); - - assert_eq!(None, iter.prev()); - assert_eq!(Some(&1), iter.next()); - assert_eq!(Column(1), iter.cur.col); - assert_eq!(4, iter.cur.line); - - assert_eq!(Some(&2), iter.next()); - assert_eq!(Some(&3), iter.next()); - assert_eq!(Some(&4), iter.next()); - - // test linewrapping - assert_eq!(Some(&5), iter.next()); - assert_eq!(Column(0), iter.cur.col); - assert_eq!(3, iter.cur.line); - - assert_eq!(Some(&4), iter.prev()); - assert_eq!(Column(4), iter.cur.col); - assert_eq!(4, iter.cur.line); - - // test that iter ends at end of grid - let mut final_iter = grid.iter_from(Point { line: 0, col: Column(4) }); - assert_eq!(None, final_iter.next()); - assert_eq!(Some(&23), final_iter.prev()); -} - -#[test] -fn shrink_reflow() { - let mut grid = Grid::new(Line(1), Column(5), 2, cell('x')); - grid[Line(0)][Column(0)] = cell('1'); - grid[Line(0)][Column(1)] = cell('2'); - grid[Line(0)][Column(2)] = cell('3'); - grid[Line(0)][Column(3)] = cell('4'); - grid[Line(0)][Column(4)] = cell('5'); - - grid.resize(Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); - - assert_eq!(grid.len(), 3); - - assert_eq!(grid[2].len(), 2); - assert_eq!(grid[2][Column(0)], cell('1')); - assert_eq!(grid[2][Column(1)], wrap_cell('2')); - - assert_eq!(grid[1].len(), 2); - assert_eq!(grid[1][Column(0)], cell('3')); - assert_eq!(grid[1][Column(1)], wrap_cell('4')); - - assert_eq!(grid[0].len(), 2); - assert_eq!(grid[0][Column(0)], cell('5')); - assert_eq!(grid[0][Column(1)], Cell::default()); -} - -#[test] -fn shrink_reflow_twice() { - let mut grid = Grid::new(Line(1), Column(5), 2, cell('x')); - grid[Line(0)][Column(0)] = cell('1'); - grid[Line(0)][Column(1)] = cell('2'); - grid[Line(0)][Column(2)] = cell('3'); - grid[Line(0)][Column(3)] = cell('4'); - grid[Line(0)][Column(4)] = cell('5'); - - grid.resize(Line(1), Column(4), &mut Point::new(Line(0), Column(0)), &Cell::default()); - grid.resize(Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); - - assert_eq!(grid.len(), 3); - - assert_eq!(grid[2].len(), 2); - assert_eq!(grid[2][Column(0)], cell('1')); - assert_eq!(grid[2][Column(1)], wrap_cell('2')); - - assert_eq!(grid[1].len(), 2); - assert_eq!(grid[1][Column(0)], cell('3')); - assert_eq!(grid[1][Column(1)], wrap_cell('4')); - - assert_eq!(grid[0].len(), 2); - assert_eq!(grid[0][Column(0)], cell('5')); - assert_eq!(grid[0][Column(1)], Cell::default()); -} - -#[test] -fn shrink_reflow_empty_cell_inside_line() { - let mut grid = Grid::new(Line(1), Column(5), 3, cell('x')); - grid[Line(0)][Column(0)] = cell('1'); - grid[Line(0)][Column(1)] = Cell::default(); - grid[Line(0)][Column(2)] = cell('3'); - grid[Line(0)][Column(3)] = cell('4'); - grid[Line(0)][Column(4)] = Cell::default(); - - grid.resize(Line(1), Column(2), &mut Point::new(Line(0), Column(0)), &Cell::default()); - - assert_eq!(grid.len(), 2); - - assert_eq!(grid[1].len(), 2); - assert_eq!(grid[1][Column(0)], cell('1')); - assert_eq!(grid[1][Column(1)], wrap_cell(' ')); - - assert_eq!(grid[0].len(), 2); - assert_eq!(grid[0][Column(0)], cell('3')); - assert_eq!(grid[0][Column(1)], cell('4')); - - grid.resize(Line(1), Column(1), &mut Point::new(Line(0), Column(0)), &Cell::default()); - - assert_eq!(grid.len(), 4); - - assert_eq!(grid[3].len(), 1); - assert_eq!(grid[3][Column(0)], wrap_cell('1')); - - assert_eq!(grid[2].len(), 1); - assert_eq!(grid[2][Column(0)], wrap_cell(' ')); - - assert_eq!(grid[1].len(), 1); - assert_eq!(grid[1][Column(0)], wrap_cell('3')); - - assert_eq!(grid[0].len(), 1); - assert_eq!(grid[0][Column(0)], cell('4')); -} - -#[test] -fn grow_reflow() { - let mut grid = Grid::new(Line(2), Column(2), 0, cell('x')); - grid[Line(0)][Column(0)] = cell('1'); - grid[Line(0)][Column(1)] = wrap_cell('2'); - grid[Line(1)][Column(0)] = cell('3'); - grid[Line(1)][Column(1)] = Cell::default(); - - grid.resize(Line(2), Column(3), &mut Point::new(Line(0), Column(0)), &Cell::default()); - - assert_eq!(grid.len(), 2); - - assert_eq!(grid[1].len(), 3); - assert_eq!(grid[1][Column(0)], cell('1')); - assert_eq!(grid[1][Column(1)], cell('2')); - assert_eq!(grid[1][Column(2)], cell('3')); - - // Make sure rest of grid is empty - assert_eq!(grid[0].len(), 3); - assert_eq!(grid[0][Column(0)], Cell::default()); - assert_eq!(grid[0][Column(1)], Cell::default()); - assert_eq!(grid[0][Column(2)], Cell::default()); -} - -#[test] -fn grow_reflow_multiline() { - let mut grid = Grid::new(Line(3), Column(2), 0, cell('x')); - grid[Line(0)][Column(0)] = cell('1'); - grid[Line(0)][Column(1)] = wrap_cell('2'); - grid[Line(1)][Column(0)] = cell('3'); - grid[Line(1)][Column(1)] = wrap_cell('4'); - grid[Line(2)][Column(0)] = cell('5'); - grid[Line(2)][Column(1)] = cell('6'); - - grid.resize(Line(3), Column(6), &mut Point::new(Line(0), Column(0)), &Cell::default()); - - assert_eq!(grid.len(), 3); - - assert_eq!(grid[2].len(), 6); - assert_eq!(grid[2][Column(0)], cell('1')); - assert_eq!(grid[2][Column(1)], cell('2')); - assert_eq!(grid[2][Column(2)], cell('3')); - assert_eq!(grid[2][Column(3)], cell('4')); - assert_eq!(grid[2][Column(4)], cell('5')); - assert_eq!(grid[2][Column(5)], cell('6')); - - // Make sure rest of grid is empty - // https://github.com/rust-lang/rust-clippy/issues/3788 - #[allow(clippy::needless_range_loop)] - for r in 0..2 { - assert_eq!(grid[r].len(), 6); - for c in 0..6 { - assert_eq!(grid[r][Column(c)], Cell::default()); - } - } -} - -fn cell(c: char) -> Cell { - let mut cell = Cell::default(); - cell.c = c; - cell -} - -fn wrap_cell(c: char) -> Cell { - let mut cell = cell(c); - cell.flags.insert(Flags::WRAPLINE); - cell -} diff --git a/src/index.rs b/src/index.rs deleted file mode 100644 index f6ea4ad3..00000000 --- a/src/index.rs +++ /dev/null @@ -1,406 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Line and Column newtypes for strongly typed tty/grid/terminal APIs - -/// Indexing types and implementations for Grid and Line -use std::cmp::{Ord, Ordering}; -use std::fmt; -use std::ops::{self, Add, AddAssign, Deref, Range, RangeInclusive, Sub, SubAssign}; - -use crate::term::RenderableCell; - -/// The side of a cell -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum Side { - Left, - Right, -} - -/// Index in the grid using row, column notation -#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize, PartialOrd)] -pub struct Point<L = Line> { - pub line: L, - pub col: Column, -} - -impl<L> Point<L> { - pub fn new(line: L, col: Column) -> Point<L> { - Point { line, col } - } -} - -impl Ord for Point { - fn cmp(&self, other: &Point) -> Ordering { - use std::cmp::Ordering::*; - match (self.line.cmp(&other.line), self.col.cmp(&other.col)) { - (Equal, Equal) => Equal, - (Equal, ord) | (ord, Equal) => ord, - (Less, _) => Less, - (Greater, _) => Greater, - } - } -} - -impl From<Point<usize>> for Point<isize> { - fn from(point: Point<usize>) -> Self { - Point::new(point.line as isize, point.col) - } -} - -impl From<Point<isize>> for Point<usize> { - fn from(point: Point<isize>) -> Self { - Point::new(point.line as usize, point.col) - } -} - -impl From<Point> for Point<usize> { - fn from(point: Point) -> Self { - Point::new(point.line.0, point.col) - } -} - -impl From<&RenderableCell> for Point<Line> { - fn from(cell: &RenderableCell) -> Self { - Point::new(cell.line, cell.column) - } -} - -/// A line -/// -/// Newtype to avoid passing values incorrectly -#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)] -pub struct Line(pub usize); - -impl fmt::Display for Line { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -/// A column -/// -/// Newtype to avoid passing values incorrectly -#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)] -pub struct Column(pub usize); - -impl fmt::Display for Column { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -/// A linear index -/// -/// Newtype to avoid passing values incorrectly -#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Ord, PartialOrd, Serialize, Deserialize)] -pub struct Linear(pub usize); - -impl Linear { - pub fn new(columns: Column, column: Column, line: Line) -> Self { - Linear(line.0 * columns.0 + column.0) - } - - pub fn from_point(columns: Column, point: Point<usize>) -> Self { - Linear(point.line * columns.0 + point.col.0) - } -} - -impl fmt::Display for Linear { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Linear({})", self.0) - } -} - -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. -// -// implements binary operators "&T op U", "T op &U", "&T op &U" -// based on "T op U" where T and U are expected to be `Copy`able -macro_rules! forward_ref_binop { - (impl $imp:ident, $method:ident for $t:ty, $u:ty) => { - impl<'a> $imp<$u> for &'a $t { - type Output = <$t as $imp<$u>>::Output; - - #[inline] - fn $method(self, other: $u) -> <$t as $imp<$u>>::Output { - $imp::$method(*self, other) - } - } - - impl<'a> $imp<&'a $u> for $t { - type Output = <$t as $imp<$u>>::Output; - - #[inline] - fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output { - $imp::$method(self, *other) - } - } - - impl<'a, 'b> $imp<&'a $u> for &'b $t { - type Output = <$t as $imp<$u>>::Output; - - #[inline] - fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output { - $imp::$method(*self, *other) - } - } - }; -} - -/// Macro for deriving deref -macro_rules! deref { - ($ty:ty, $target:ty) => { - impl Deref for $ty { - type Target = $target; - - #[inline] - fn deref(&self) -> &$target { - &self.0 - } - } - }; -} - -macro_rules! add { - ($ty:ty, $construct:expr) => { - impl ops::Add<$ty> for $ty { - type Output = $ty; - - #[inline] - fn add(self, rhs: $ty) -> $ty { - $construct(self.0 + rhs.0) - } - } - }; -} - -macro_rules! sub { - ($ty:ty, $construct:expr) => { - impl ops::Sub<$ty> for $ty { - type Output = $ty; - - #[inline] - fn sub(self, rhs: $ty) -> $ty { - $construct(self.0 - rhs.0) - } - } - - impl<'a> ops::Sub<$ty> for &'a $ty { - type Output = $ty; - - #[inline] - fn sub(self, rhs: $ty) -> $ty { - $construct(self.0 - rhs.0) - } - } - - impl<'a> ops::Sub<&'a $ty> for $ty { - type Output = $ty; - - #[inline] - fn sub(self, rhs: &'a $ty) -> $ty { - $construct(self.0 - rhs.0) - } - } - - impl<'a, 'b> ops::Sub<&'a $ty> for &'b $ty { - type Output = $ty; - - #[inline] - fn sub(self, rhs: &'a $ty) -> $ty { - $construct(self.0 - rhs.0) - } - } - }; -} - -/// This exists because we can't implement Iterator on Range -/// and the existing impl needs the unstable Step trait -/// This should be removed and replaced with a Step impl -/// in the ops macro when `step_by` is stabilized -pub struct IndexRange<T>(pub Range<T>); - -impl<T> From<Range<T>> for IndexRange<T> { - fn from(from: Range<T>) -> Self { - IndexRange(from) - } -} - -// can be removed if range_contains is stabilized -pub trait Contains { - type Content; - fn contains_(&self, item: Self::Content) -> bool; -} - -impl<T: PartialOrd<T>> Contains for Range<T> { - type Content = T; - - fn contains_(&self, item: Self::Content) -> bool { - (self.start <= item) && (item < self.end) - } -} - -impl<T: PartialOrd<T>> Contains for RangeInclusive<T> { - type Content = T; - - fn contains_(&self, item: Self::Content) -> bool { - (self.start() <= &item) && (&item <= self.end()) - } -} - -macro_rules! ops { - ($ty:ty, $construct:expr) => { - add!($ty, $construct); - sub!($ty, $construct); - deref!($ty, usize); - forward_ref_binop!(impl Add, add for $ty, $ty); - - impl $ty { - #[inline] - fn steps_between(start: $ty, end: $ty, by: $ty) -> Option<usize> { - if by == $construct(0) { return None; } - if start < end { - // Note: We assume $t <= usize here - let diff = (end - start).0; - let by = by.0; - if diff % by > 0 { - Some(diff / by + 1) - } else { - Some(diff / by) - } - } else { - Some(0) - } - } - - #[inline] - fn steps_between_by_one(start: $ty, end: $ty) -> Option<usize> { - Self::steps_between(start, end, $construct(1)) - } - } - - impl Iterator for IndexRange<$ty> { - type Item = $ty; - #[inline] - fn next(&mut self) -> Option<$ty> { - if self.0.start < self.0.end { - let old = self.0.start; - self.0.start = old + 1; - Some(old) - } else { - None - } - } - #[inline] - fn size_hint(&self) -> (usize, Option<usize>) { - match Self::Item::steps_between_by_one(self.0.start, self.0.end) { - Some(hint) => (hint, Some(hint)), - None => (0, None) - } - } - } - - impl DoubleEndedIterator for IndexRange<$ty> { - #[inline] - fn next_back(&mut self) -> Option<$ty> { - if self.0.start < self.0.end { - let new = self.0.end - 1; - self.0.end = new; - Some(new) - } else { - None - } - } - } - impl AddAssign<$ty> for $ty { - #[inline] - fn add_assign(&mut self, rhs: $ty) { - self.0 += rhs.0 - } - } - - impl SubAssign<$ty> for $ty { - #[inline] - fn sub_assign(&mut self, rhs: $ty) { - self.0 -= rhs.0 - } - } - - impl AddAssign<usize> for $ty { - #[inline] - fn add_assign(&mut self, rhs: usize) { - self.0 += rhs - } - } - - impl SubAssign<usize> for $ty { - #[inline] - fn sub_assign(&mut self, rhs: usize) { - self.0 -= rhs - } - } - - impl From<usize> for $ty { - #[inline] - fn from(val: usize) -> $ty { - $construct(val) - } - } - - impl Add<usize> for $ty { - type Output = $ty; - - #[inline] - fn add(self, rhs: usize) -> $ty { - $construct(self.0 + rhs) - } - } - - impl Sub<usize> for $ty { - type Output = $ty; - - #[inline] - fn sub(self, rhs: usize) -> $ty { - $construct(self.0 - rhs) - } - } - } -} - -ops!(Line, Line); -ops!(Column, Column); -ops!(Linear, Linear); - -#[cfg(test)] -mod tests { - use super::{Column, Line, Point}; - - #[test] - fn location_ordering() { - assert!(Point::new(Line(0), Column(0)) == Point::new(Line(0), Column(0))); - assert!(Point::new(Line(1), Column(0)) > Point::new(Line(0), Column(0))); - assert!(Point::new(Line(0), Column(1)) > Point::new(Line(0), Column(0))); - assert!(Point::new(Line(1), Column(1)) > Point::new(Line(0), Column(0))); - assert!(Point::new(Line(1), Column(1)) > Point::new(Line(0), Column(1))); - assert!(Point::new(Line(1), Column(1)) > Point::new(Line(1), Column(0))); - } -} diff --git a/src/input.rs b/src/input.rs deleted file mode 100644 index fc79b398..00000000 --- a/src/input.rs +++ /dev/null @@ -1,1300 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! Handle input from glutin -//! -//! Certain key combinations should send some escape sequence back to the pty. -//! In order to figure that out, state about which modifier keys are pressed -//! needs to be tracked. Additionally, we need a bit of a state machine to -//! determine what to do when a non-modifier key is pressed. -use std::borrow::Cow; -use std::mem; -use std::ops::RangeInclusive; -use std::time::Instant; - -use copypasta::{Buffer as ClipboardBuffer, Clipboard, Load}; -use glutin::{ - ElementState, KeyboardInput, ModifiersState, MouseButton, MouseCursor, MouseScrollDelta, - TouchPhase, -}; -use unicode_width::UnicodeWidthStr; - -use crate::ansi::{ClearMode, Handler}; -use crate::config::{self, Key}; -use crate::event::{ClickState, Mouse}; -use crate::grid::Scroll; -use crate::index::{Column, Line, Linear, Point, Side}; -use crate::message_bar::{self, Message}; -use crate::term::mode::TermMode; -use crate::term::{Search, SizeInfo, Term}; -use crate::url::Url; -use crate::util::fmt::Red; -use crate::util::start_daemon; - -pub const FONT_SIZE_STEP: f32 = 0.5; - -/// Processes input from glutin. -/// -/// An escape sequence may be emitted in case specific keys or key combinations -/// are activated. -/// -/// TODO also need terminal state when processing input -pub struct Processor<'a, A: 'a> { - pub key_bindings: &'a [KeyBinding], - pub mouse_bindings: &'a [MouseBinding], - pub mouse_config: &'a config::Mouse, - pub scrolling_config: &'a config::Scrolling, - pub ctx: A, - pub save_to_clipboard: bool, - pub alt_send_esc: bool, -} - -pub trait ActionContext { - fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _: B); - fn size_info(&self) -> SizeInfo; - fn copy_selection(&self, _: ClipboardBuffer); - fn clear_selection(&mut self); - fn update_selection(&mut self, point: Point, side: Side); - fn simple_selection(&mut self, point: Point, side: Side); - fn semantic_selection(&mut self, point: Point); - fn line_selection(&mut self, point: Point); - fn selection_is_empty(&self) -> bool; - fn mouse_mut(&mut self) -> &mut Mouse; - fn mouse(&self) -> &Mouse; - fn mouse_coords(&self) -> Option<Point>; - fn received_count(&mut self) -> &mut usize; - fn suppress_chars(&mut self) -> &mut bool; - fn last_modifiers(&mut self) -> &mut ModifiersState; - fn scroll(&mut self, scroll: Scroll); - fn hide_window(&mut self); - fn terminal(&self) -> &Term; - fn terminal_mut(&mut self) -> &mut Term; - fn spawn_new_instance(&mut self); - fn toggle_fullscreen(&mut self); - #[cfg(target_os = "macos")] - fn toggle_simple_fullscreen(&mut self); -} - -/// Describes a state and action to take in that state -/// -/// This is the shared component of `MouseBinding` and `KeyBinding` -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Binding<T> { - /// Modifier keys required to activate binding - pub mods: ModifiersState, - - /// String to send to pty if mods and mode match - pub action: Action, - - /// Terminal mode required to activate binding - pub mode: TermMode, - - /// excluded terminal modes where the binding won't be activated - pub notmode: TermMode, - - /// This property is used as part of the trigger detection code. - /// - /// For example, this might be a key like "G", or a mouse button. - pub trigger: T, -} - -/// Bindings that are triggered by a keyboard key -pub type KeyBinding = Binding<Key>; - -/// Bindings that are triggered by a mouse button -pub type MouseBinding = Binding<MouseButton>; - -impl Default for KeyBinding { - fn default() -> KeyBinding { - KeyBinding { - mods: Default::default(), - action: Action::Esc(String::new()), - mode: TermMode::NONE, - notmode: TermMode::NONE, - trigger: Key::A, - } - } -} - -impl Default for MouseBinding { - fn default() -> MouseBinding { - MouseBinding { - mods: Default::default(), - action: Action::Esc(String::new()), - mode: TermMode::NONE, - notmode: TermMode::NONE, - trigger: MouseButton::Left, - } - } -} - -impl<T: Eq> Binding<T> { - #[inline] - fn is_triggered_by( - &self, - mode: TermMode, - mods: ModifiersState, - input: &T, - relaxed: bool, - ) -> bool { - // Check input first since bindings are stored in one big list. This is - // the most likely item to fail so prioritizing it here allows more - // checks to be short circuited. - self.trigger == *input - && self.mode_matches(mode) - && self.not_mode_matches(mode) - && self.mods_match(mods, relaxed) - } - - #[inline] - pub fn triggers_match(&self, binding: &Binding<T>) -> bool { - self.trigger == binding.trigger - && self.mode == binding.mode - && self.notmode == binding.notmode - && self.mods == binding.mods - } -} - -impl<T> Binding<T> { - /// Execute the action associate with this binding - #[inline] - fn execute<A: ActionContext>(&self, ctx: &mut A, mouse_mode: bool) { - self.action.execute(ctx, mouse_mode) - } - - #[inline] - fn mode_matches(&self, mode: TermMode) -> bool { - self.mode.is_empty() || mode.intersects(self.mode) - } - - #[inline] - fn not_mode_matches(&self, mode: TermMode) -> bool { - self.notmode.is_empty() || !mode.intersects(self.notmode) - } - - /// Check that two mods descriptions for equivalence - #[inline] - fn mods_match(&self, mods: ModifiersState, relaxed: bool) -> bool { - if relaxed { - self.mods.relaxed_eq(mods) - } else { - self.mods == mods - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Action { - /// Write an escape sequence - Esc(String), - - /// Paste contents of system clipboard - Paste, - - // Store current selection into clipboard - Copy, - - /// Paste contents of selection buffer - PasteSelection, - - /// Increase font size - IncreaseFontSize, - - /// Decrease font size - DecreaseFontSize, - - /// Reset font size to the config value - ResetFontSize, - - /// Scroll exactly one page up - ScrollPageUp, - - /// Scroll exactly one page down - ScrollPageDown, - - /// Scroll one line up - ScrollLineUp, - - /// Scroll one line down - ScrollLineDown, - - /// Scroll all the way to the top - ScrollToTop, - - /// Scroll all the way to the bottom - ScrollToBottom, - - /// Clear the display buffer(s) to remove history - ClearHistory, - - /// Run given command - Command(String, Vec<String>), - - /// Hides the Alacritty window - Hide, - - /// Quits Alacritty. - Quit, - - /// Clears warning and error notices. - ClearLogNotice, - - /// Spawn a new instance of Alacritty. - SpawnNewInstance, - - /// Toggle fullscreen. - ToggleFullscreen, - - /// Toggle simple fullscreen on macos. - #[cfg(target_os = "macos")] - ToggleSimpleFullscreen, - - /// No action. - None, -} - -impl Default for Action { - fn default() -> Action { - Action::None - } -} - -impl Action { - #[inline] - fn execute<A: ActionContext>(&self, ctx: &mut A, mouse_mode: bool) { - match *self { - Action::Esc(ref s) => { - ctx.scroll(Scroll::Bottom); - ctx.write_to_pty(s.clone().into_bytes()) - }, - Action::Copy => { - ctx.copy_selection(ClipboardBuffer::Primary); - }, - Action::Paste => { - Clipboard::new() - .and_then(|clipboard| clipboard.load_primary()) - .map(|contents| self.paste(ctx, &contents)) - .unwrap_or_else(|err| { - error!("Error loading data from clipboard: {}", Red(err)); - }); - }, - Action::PasteSelection => { - // Only paste if mouse events are not captured by an application - if !mouse_mode { - Clipboard::new() - .and_then(|clipboard| clipboard.load_selection()) - .map(|contents| self.paste(ctx, &contents)) - .unwrap_or_else(|err| { - error!("Error loading data from clipboard: {}", Red(err)); - }); - } - }, - Action::Command(ref program, ref args) => { - trace!("Running command {} with args {:?}", program, args); - - match start_daemon(program, args) { - Ok(_) => { - debug!("Spawned new proc"); - }, - Err(err) => { - warn!("Couldn't run command {}", err); - }, - } - }, - Action::ToggleFullscreen => { - ctx.toggle_fullscreen(); - }, - #[cfg(target_os = "macos")] - Action::ToggleSimpleFullscreen => { - ctx.toggle_simple_fullscreen(); - }, - Action::Hide => { - ctx.hide_window(); - }, - Action::Quit => { - ctx.terminal_mut().exit(); - }, - Action::IncreaseFontSize => { - ctx.terminal_mut().change_font_size(FONT_SIZE_STEP); - }, - Action::DecreaseFontSize => { - ctx.terminal_mut().change_font_size(-FONT_SIZE_STEP); - }, - Action::ResetFontSize => { - ctx.terminal_mut().reset_font_size(); - }, - Action::ScrollPageUp => { - ctx.scroll(Scroll::PageUp); - }, - Action::ScrollPageDown => { - ctx.scroll(Scroll::PageDown); - }, - Action::ScrollLineUp => { - ctx.scroll(Scroll::Lines(1)); - }, - Action::ScrollLineDown => { - ctx.scroll(Scroll::Lines(-1)); - }, - Action::ScrollToTop => { - ctx.scroll(Scroll::Top); - }, - Action::ScrollToBottom => { - ctx.scroll(Scroll::Bottom); - }, - Action::ClearHistory => { - ctx.terminal_mut().clear_screen(ClearMode::Saved); - }, - Action::ClearLogNotice => { - ctx.terminal_mut().message_buffer_mut().pop(); - }, - Action::SpawnNewInstance => { - ctx.spawn_new_instance(); - }, - Action::None => (), - } - } - - fn paste<A: ActionContext>(&self, ctx: &mut A, contents: &str) { - if ctx.terminal().mode().contains(TermMode::BRACKETED_PASTE) { - ctx.write_to_pty(&b"\x1b[200~"[..]); - ctx.write_to_pty(contents.replace("\x1b", "").into_bytes()); - ctx.write_to_pty(&b"\x1b[201~"[..]); - } else { - // In non-bracketed (ie: normal) mode, terminal applications cannot distinguish - // pasted data from keystrokes. - // In theory, we should construct the keystrokes needed to produce the data we are - // pasting... since that's neither practical nor sensible (and probably an impossible - // task to solve in a general way), we'll just replace line breaks (windows and unix - // style) with a singe carriage return (\r, which is what the Enter key produces). - ctx.write_to_pty(contents.replace("\r\n", "\r").replace("\n", "\r").into_bytes()); - } - } -} - -trait RelaxedEq<T: ?Sized = Self> { - fn relaxed_eq(&self, other: T) -> bool; -} - -impl RelaxedEq for ModifiersState { - // Make sure that modifiers in the config are always present, - // but ignore surplus modifiers. - fn relaxed_eq(&self, other: Self) -> bool { - (!self.logo || other.logo) - && (!self.alt || other.alt) - && (!self.ctrl || other.ctrl) - && (!self.shift || other.shift) - } -} - -impl From<&'static str> for Action { - fn from(s: &'static str) -> Action { - Action::Esc(s.into()) - } -} - -impl<'a, A: ActionContext + 'a> Processor<'a, A> { - #[inline] - pub fn mouse_moved(&mut self, x: usize, y: usize, modifiers: ModifiersState) { - self.ctx.mouse_mut().x = x; - self.ctx.mouse_mut().y = y; - - let size_info = self.ctx.size_info(); - let point = size_info.pixels_to_coords(x, y); - - let cell_side = self.get_mouse_side(); - let prev_side = mem::replace(&mut self.ctx.mouse_mut().cell_side, cell_side); - let prev_line = mem::replace(&mut self.ctx.mouse_mut().line, point.line); - let prev_col = mem::replace(&mut self.ctx.mouse_mut().column, point.col); - - let motion_mode = TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG; - let report_mode = TermMode::MOUSE_REPORT_CLICK | motion_mode; - - let mouse_moved = prev_line != self.ctx.mouse().line - || prev_col != self.ctx.mouse().column - || prev_side != cell_side; - - // Don't launch URLs if mouse has moved - if mouse_moved { - self.ctx.mouse_mut().block_url_launcher = true; - } - - // Only report motions when cell changed and mouse is not over the message bar - if self.message_at_point(Some(point)).is_some() || !mouse_moved { - return; - } - - // Underline URLs and change cursor on hover - self.update_url_highlight(point, modifiers); - - if self.ctx.mouse().left_button_state == ElementState::Pressed - && (modifiers.shift || !self.ctx.terminal().mode().intersects(report_mode)) - { - self.ctx.update_selection(Point { line: point.line, col: point.col }, cell_side); - } else if self.ctx.terminal().mode().intersects(motion_mode) - && size_info.contains_point(x, y, false) - { - if self.ctx.mouse().left_button_state == ElementState::Pressed { - self.mouse_report(32, ElementState::Pressed, modifiers); - } else if self.ctx.mouse().middle_button_state == ElementState::Pressed { - self.mouse_report(33, ElementState::Pressed, modifiers); - } else if self.ctx.mouse().right_button_state == ElementState::Pressed { - self.mouse_report(34, ElementState::Pressed, modifiers); - } else if self.ctx.terminal().mode().contains(TermMode::MOUSE_MOTION) { - self.mouse_report(35, ElementState::Pressed, modifiers); - } - } - } - - /// Underline URLs and change the mouse cursor when URL hover state changes. - fn update_url_highlight(&mut self, point: Point, modifiers: ModifiersState) { - let mouse_mode = - TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG | TermMode::MOUSE_REPORT_CLICK; - - // Only show URLs as launchable when all required modifiers are pressed - let url = if self.mouse_config.url.modifiers.relaxed_eq(modifiers) - && (!self.ctx.terminal().mode().intersects(mouse_mode) || modifiers.shift) - && self.mouse_config.url.launcher.is_some() - { - self.ctx.terminal().url_search(point.into()) - } else { - None - }; - - if let Some(Url { origin, text }) = url { - let cols = self.ctx.size_info().cols().0; - - // Calculate the URL's start position - let lines_before = (origin + cols - point.col.0 - 1) / cols; - let (start_col, start_line) = if lines_before > point.line.0 { - (0, 0) - } else { - let start_col = (cols + point.col.0 - origin % cols) % cols; - let start_line = point.line.0 - lines_before; - (start_col, start_line) - }; - let start = Point::new(start_line, Column(start_col)); - - // Calculate the URL's end position - let len = text.width(); - let end_col = (point.col.0 + len - origin) % cols - 1; - let end_line = point.line.0 + (point.col.0 + len - origin) / cols; - let end = Point::new(end_line, Column(end_col)); - - let start = Linear::from_point(Column(cols), start); - let end = Linear::from_point(Column(cols), end); - - self.ctx.terminal_mut().set_url_highlight(RangeInclusive::new(start, end)); - self.ctx.terminal_mut().set_mouse_cursor(MouseCursor::Hand); - self.ctx.terminal_mut().dirty = true; - } else { - self.ctx.terminal_mut().reset_url_highlight(); - } - } - - fn get_mouse_side(&self) -> Side { - let size_info = self.ctx.size_info(); - let x = self.ctx.mouse().x; - - let cell_x = x.saturating_sub(size_info.padding_x as usize) % size_info.cell_width as usize; - let half_cell_width = (size_info.cell_width / 2.0) as usize; - - let additional_padding = - (size_info.width - size_info.padding_x * 2.) % size_info.cell_width; - let end_of_grid = size_info.width - size_info.padding_x - additional_padding; - - if cell_x > half_cell_width - // Edge case when mouse leaves the window - || x as f32 >= end_of_grid - { - Side::Right - } else { - Side::Left - } - } - - pub fn normal_mouse_report(&mut self, button: u8) { - let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column); - - if line < Line(223) && column < Column(223) { - let msg = vec![ - b'\x1b', - b'[', - b'M', - 32 + button, - 32 + 1 + column.0 as u8, - 32 + 1 + line.0 as u8, - ]; - - self.ctx.write_to_pty(msg); - } - } - - pub fn sgr_mouse_report(&mut self, button: u8, state: ElementState) { - let (line, column) = (self.ctx.mouse().line, self.ctx.mouse().column); - let c = match state { - ElementState::Pressed => 'M', - ElementState::Released => 'm', - }; - - let msg = format!("\x1b[<{};{};{}{}", button, column + 1, line + 1, c); - self.ctx.write_to_pty(msg.into_bytes()); - } - - pub fn mouse_report(&mut self, button: u8, state: ElementState, modifiers: ModifiersState) { - // Calculate modifiers value - let mut mods = 0; - if modifiers.shift { - mods += 4; - } - if modifiers.alt { - mods += 8; - } - if modifiers.ctrl { - mods += 16; - } - - // Report mouse events - if self.ctx.terminal().mode().contains(TermMode::SGR_MOUSE) { - self.sgr_mouse_report(button + mods, state); - } else if let ElementState::Released = state { - self.normal_mouse_report(3 + mods); - } else { - self.normal_mouse_report(button + mods); - } - } - - pub fn on_mouse_double_click(&mut self, button: MouseButton, point: Option<Point>) { - if let (Some(point), true) = (point, button == MouseButton::Left) { - self.ctx.semantic_selection(point); - } - } - - pub fn on_mouse_triple_click(&mut self, button: MouseButton, point: Option<Point>) { - if let (Some(point), true) = (point, button == MouseButton::Left) { - self.ctx.line_selection(point); - } - } - - pub fn on_mouse_press( - &mut self, - button: MouseButton, - modifiers: ModifiersState, - point: Option<Point>, - ) { - let now = Instant::now(); - let elapsed = self.ctx.mouse().last_click_timestamp.elapsed(); - self.ctx.mouse_mut().last_click_timestamp = now; - - let button_changed = self.ctx.mouse().last_button != button; - - self.ctx.mouse_mut().click_state = match self.ctx.mouse().click_state { - ClickState::Click - if !button_changed && elapsed < self.mouse_config.double_click.threshold => - { - self.ctx.mouse_mut().block_url_launcher = true; - self.on_mouse_double_click(button, point); - ClickState::DoubleClick - } - ClickState::DoubleClick - if !button_changed && elapsed < self.mouse_config.triple_click.threshold => - { - self.ctx.mouse_mut().block_url_launcher = true; - self.on_mouse_triple_click(button, point); - ClickState::TripleClick - } - _ => { - // Don't launch URLs if this click cleared the selection - self.ctx.mouse_mut().block_url_launcher = !self.ctx.selection_is_empty(); - - self.ctx.clear_selection(); - - // Start new empty selection - let side = self.ctx.mouse().cell_side; - if let Some(point) = point { - self.ctx.simple_selection(point, side); - } - - let report_modes = - TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) { - let code = match button { - MouseButton::Left => 0, - MouseButton::Middle => 1, - MouseButton::Right => 2, - // Can't properly report more than three buttons. - MouseButton::Other(_) => return, - }; - self.mouse_report(code, ElementState::Pressed, modifiers); - return; - } - - ClickState::Click - }, - }; - } - - pub fn on_mouse_release( - &mut self, - button: MouseButton, - modifiers: ModifiersState, - point: Option<Point>, - ) { - let report_modes = - TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - if !modifiers.shift && self.ctx.terminal().mode().intersects(report_modes) { - let code = match button { - MouseButton::Left => 0, - MouseButton::Middle => 1, - MouseButton::Right => 2, - // Can't properly report more than three buttons. - MouseButton::Other(_) => return, - }; - self.mouse_report(code, ElementState::Released, modifiers); - return; - } else if let (Some(point), true) = (point, button == MouseButton::Left) { - self.launch_url(modifiers, point); - } - - self.copy_selection(); - } - - // Spawn URL launcher when clicking on URLs - fn launch_url(&self, modifiers: ModifiersState, point: Point) -> Option<()> { - if !self.mouse_config.url.modifiers.relaxed_eq(modifiers) - || self.ctx.mouse().block_url_launcher - { - return None; - } - - let text = self.ctx.terminal().url_search(point.into())?.text; - - let launcher = self.mouse_config.url.launcher.as_ref()?; - let mut args = launcher.args().to_vec(); - args.push(text); - - match start_daemon(launcher.program(), &args) { - Ok(_) => debug!("Launched {} with args {:?}", launcher.program(), args), - Err(_) => warn!("Unable to launch {} with args {:?}", launcher.program(), args), - } - - Some(()) - } - - pub fn on_mouse_wheel( - &mut self, - delta: MouseScrollDelta, - phase: TouchPhase, - modifiers: ModifiersState, - ) { - match delta { - MouseScrollDelta::LineDelta(_columns, lines) => { - let new_scroll_px = lines * self.ctx.size_info().cell_height; - self.scroll_terminal(modifiers, new_scroll_px as i32); - }, - MouseScrollDelta::PixelDelta(lpos) => { - match phase { - TouchPhase::Started => { - // Reset offset to zero - self.ctx.mouse_mut().scroll_px = 0; - }, - TouchPhase::Moved => { - self.scroll_terminal(modifiers, lpos.y as i32); - }, - _ => (), - } - }, - } - } - - fn scroll_terminal(&mut self, modifiers: ModifiersState, new_scroll_px: i32) { - let mouse_modes = - TermMode::MOUSE_REPORT_CLICK | TermMode::MOUSE_DRAG | TermMode::MOUSE_MOTION; - let height = self.ctx.size_info().cell_height as i32; - - // Make sure the new and deprecated setting are both allowed - let faux_multiplier = self - .mouse_config - .faux_scrollback_lines - .unwrap_or(self.scrolling_config.faux_multiplier as usize); - - if self.ctx.terminal().mode().intersects(mouse_modes) { - self.ctx.mouse_mut().scroll_px += new_scroll_px; - - let code = if new_scroll_px > 0 { 64 } else { 65 }; - let lines = (self.ctx.mouse().scroll_px / height).abs(); - - for _ in 0..lines { - self.mouse_report(code, ElementState::Pressed, modifiers); - } - } else if self.ctx.terminal().mode().contains(TermMode::ALT_SCREEN) - && faux_multiplier > 0 - && !modifiers.shift - { - self.ctx.mouse_mut().scroll_px += new_scroll_px * faux_multiplier as i32; - - let cmd = if new_scroll_px > 0 { b'A' } else { b'B' }; - let lines = (self.ctx.mouse().scroll_px / height).abs(); - - let mut content = Vec::with_capacity(lines as usize * 3); - for _ in 0..lines { - content.push(0x1b); - content.push(b'O'); - content.push(cmd); - } - self.ctx.write_to_pty(content); - } else { - let multiplier = i32::from(self.scrolling_config.multiplier); - self.ctx.mouse_mut().scroll_px += new_scroll_px * multiplier; - - let lines = self.ctx.mouse().scroll_px / height; - - self.ctx.scroll(Scroll::Lines(lines as isize)); - } - - self.ctx.mouse_mut().scroll_px %= height; - } - - pub fn on_focus_change(&mut self, is_focused: bool) { - if self.ctx.terminal().mode().contains(TermMode::FOCUS_IN_OUT) { - let chr = if is_focused { "I" } else { "O" }; - - let msg = format!("\x1b[{}", chr); - self.ctx.write_to_pty(msg.into_bytes()); - } - } - - pub fn mouse_input( - &mut self, - state: ElementState, - button: MouseButton, - modifiers: ModifiersState, - ) { - match button { - MouseButton::Left => self.ctx.mouse_mut().left_button_state = state, - MouseButton::Middle => self.ctx.mouse_mut().middle_button_state = state, - MouseButton::Right => self.ctx.mouse_mut().right_button_state = state, - _ => (), - } - - let point = self.ctx.mouse_coords(); - - // Skip normal mouse events if the message bar has been clicked - if let Some(message) = self.message_at_point(point) { - // Message should never be `Some` if point is `None` - debug_assert!(point.is_some()); - self.on_message_bar_click(state, point.unwrap(), message); - } else { - match state { - ElementState::Pressed => { - self.process_mouse_bindings(modifiers, button); - self.on_mouse_press(button, modifiers, point); - }, - ElementState::Released => self.on_mouse_release(button, modifiers, point), - } - } - - self.ctx.mouse_mut().last_button = button; - } - - /// Process key input - pub fn process_key(&mut self, input: KeyboardInput) { - match input.state { - ElementState::Pressed => { - *self.ctx.last_modifiers() = input.modifiers; - *self.ctx.received_count() = 0; - *self.ctx.suppress_chars() = false; - - if self.process_key_bindings(input) { - *self.ctx.suppress_chars() = true; - } - }, - ElementState::Released => *self.ctx.suppress_chars() = false, - } - } - - /// Process a received character - pub fn received_char(&mut self, c: char) { - if *self.ctx.suppress_chars() { - return; - } - - self.ctx.scroll(Scroll::Bottom); - self.ctx.clear_selection(); - - let utf8_len = c.len_utf8(); - let mut bytes = Vec::with_capacity(utf8_len); - unsafe { - bytes.set_len(utf8_len); - c.encode_utf8(&mut bytes[..]); - } - - if self.alt_send_esc - && *self.ctx.received_count() == 0 - && self.ctx.last_modifiers().alt - && utf8_len == 1 - { - bytes.insert(0, b'\x1b'); - } - - self.ctx.write_to_pty(bytes); - - *self.ctx.received_count() += 1; - } - - /// Attempts to find a binding and execute its action - /// - /// The provided mode, mods, and key must match what is allowed by a binding - /// for its action to be executed. - /// - /// Returns true if an action is executed. - fn process_key_bindings(&mut self, input: KeyboardInput) -> bool { - let mut has_binding = false; - for binding in self.key_bindings { - let is_triggered = match binding.trigger { - Key::Scancode(_) => binding.is_triggered_by( - *self.ctx.terminal().mode(), - input.modifiers, - &Key::Scancode(input.scancode), - false, - ), - _ => { - if let Some(key) = input.virtual_keycode { - let key = Key::from_glutin_input(key); - binding.is_triggered_by( - *self.ctx.terminal().mode(), - input.modifiers, - &key, - false, - ) - } else { - false - } - }, - }; - - if is_triggered { - // binding was triggered; run the action - binding.execute(&mut self.ctx, false); - has_binding = true; - } - } - - has_binding - } - - /// Attempts to find a binding and execute its action - /// - /// The provided mode, mods, and key must match what is allowed by a binding - /// for its action to be executed. - /// - /// Returns true if an action is executed. - fn process_mouse_bindings(&mut self, mods: ModifiersState, button: MouseButton) -> bool { - let mut has_binding = false; - for binding in self.mouse_bindings { - if binding.is_triggered_by(*self.ctx.terminal().mode(), mods, &button, true) { - // binding was triggered; run the action - let mouse_mode = !mods.shift - && self.ctx.terminal().mode().intersects( - TermMode::MOUSE_REPORT_CLICK - | TermMode::MOUSE_DRAG - | TermMode::MOUSE_MOTION, - ); - binding.execute(&mut self.ctx, mouse_mode); - has_binding = true; - } - } - - has_binding - } - - /// Return the message bar's message if there is some at the specified point - fn message_at_point(&mut self, point: Option<Point>) -> Option<Message> { - if let (Some(point), Some(message)) = - (point, self.ctx.terminal_mut().message_buffer_mut().message()) - { - let size = self.ctx.size_info(); - if point.line.0 >= size.lines().saturating_sub(message.text(&size).len()) { - return Some(message); - } - } - - None - } - - /// Handle clicks on the message bar. - fn on_message_bar_click(&mut self, button_state: ElementState, point: Point, message: Message) { - match button_state { - ElementState::Released => self.copy_selection(), - ElementState::Pressed => { - let size = self.ctx.size_info(); - if point.col + message_bar::CLOSE_BUTTON_TEXT.len() >= size.cols() - && point.line == size.lines() - message.text(&size).len() - { - self.ctx.terminal_mut().message_buffer_mut().pop(); - } - - self.ctx.clear_selection(); - }, - } - } - - /// Copy text selection. - fn copy_selection(&mut self) { - if self.save_to_clipboard { - self.ctx.copy_selection(ClipboardBuffer::Primary); - } - self.ctx.copy_selection(ClipboardBuffer::Selection); - } -} - -#[cfg(test)] -mod tests { - use std::borrow::Cow; - use std::time::Duration; - - use glutin::{ElementState, Event, ModifiersState, MouseButton, VirtualKeyCode, WindowEvent}; - - use crate::config::{self, ClickHandler, Config}; - use crate::event::{ClickState, Mouse, WindowChanges}; - use crate::grid::Scroll; - use crate::index::{Point, Side}; - use crate::message_bar::MessageBuffer; - use crate::selection::Selection; - use crate::term::{SizeInfo, Term, TermMode}; - - use super::{Action, Binding, Processor}; - use copypasta::Buffer as ClipboardBuffer; - - const KEY: VirtualKeyCode = VirtualKeyCode::Key0; - - #[derive(PartialEq)] - enum MultiClick { - DoubleClick, - TripleClick, - None, - } - - struct ActionContext<'a> { - pub terminal: &'a mut Term, - pub selection: &'a mut Option<Selection>, - pub size_info: &'a SizeInfo, - pub mouse: &'a mut Mouse, - pub last_action: MultiClick, - pub received_count: usize, - pub suppress_chars: bool, - pub last_modifiers: ModifiersState, - pub window_changes: &'a mut WindowChanges, - } - - impl<'a> super::ActionContext for ActionContext<'a> { - fn write_to_pty<B: Into<Cow<'static, [u8]>>>(&mut self, _val: B) {} - - fn update_selection(&mut self, _point: Point, _side: Side) {} - - fn simple_selection(&mut self, _point: Point, _side: Side) {} - - fn copy_selection(&self, _buffer: ClipboardBuffer) {} - - fn clear_selection(&mut self) {} - - fn hide_window(&mut self) {} - - fn spawn_new_instance(&mut self) {} - - fn toggle_fullscreen(&mut self) {} - - #[cfg(target_os = "macos")] - fn toggle_simple_fullscreen(&mut self) {} - - fn terminal(&self) -> &Term { - &self.terminal - } - - fn terminal_mut(&mut self) -> &mut Term { - &mut self.terminal - } - - fn size_info(&self) -> SizeInfo { - *self.size_info - } - - fn semantic_selection(&mut self, _point: Point) { - // set something that we can check for here - self.last_action = MultiClick::DoubleClick; - } - - fn line_selection(&mut self, _point: Point) { - self.last_action = MultiClick::TripleClick; - } - - fn selection_is_empty(&self) -> bool { - true - } - - fn scroll(&mut self, scroll: Scroll) { - self.terminal.scroll_display(scroll); - } - - fn mouse_coords(&self) -> Option<Point> { - self.terminal.pixels_to_coords(self.mouse.x as usize, self.mouse.y as usize) - } - - #[inline] - fn mouse_mut(&mut self) -> &mut Mouse { - self.mouse - } - - #[inline] - fn mouse(&self) -> &Mouse { - self.mouse - } - - fn received_count(&mut self) -> &mut usize { - &mut self.received_count - } - - fn suppress_chars(&mut self) -> &mut bool { - &mut self.suppress_chars - } - - fn last_modifiers(&mut self) -> &mut ModifiersState { - &mut self.last_modifiers - } - } - - macro_rules! test_clickstate { - { - name: $name:ident, - initial_state: $initial_state:expr, - initial_button: $initial_button:expr, - input: $input:expr, - end_state: $end_state:pat, - last_action: $last_action:expr - } => { - #[test] - fn $name() { - let config = Config::default(); - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - - let mut terminal = Term::new(&config, size, MessageBuffer::new()); - - let mut mouse = Mouse::default(); - mouse.click_state = $initial_state; - mouse.last_button = $initial_button; - - let mut selection = None; - - let context = ActionContext { - terminal: &mut terminal, - selection: &mut selection, - mouse: &mut mouse, - size_info: &size, - last_action: MultiClick::None, - received_count: 0, - suppress_chars: false, - last_modifiers: ModifiersState::default(), - window_changes: &mut WindowChanges::default(), - }; - - let mut processor = Processor { - ctx: context, - mouse_config: &config::Mouse { - double_click: ClickHandler { - threshold: Duration::from_millis(1000), - }, - triple_click: ClickHandler { - threshold: Duration::from_millis(1000), - }, - hide_when_typing: false, - faux_scrollback_lines: None, - url: Default::default(), - }, - scrolling_config: &config::Scrolling::default(), - key_bindings: &config.key_bindings()[..], - mouse_bindings: &config.mouse_bindings()[..], - save_to_clipboard: config.selection().save_to_clipboard, - alt_send_esc: config.alt_send_esc(), - }; - - if let Event::WindowEvent { event: WindowEvent::MouseInput { state, button, modifiers, .. }, .. } = $input { - processor.mouse_input(state, button, modifiers); - }; - - assert!(match processor.ctx.mouse.click_state { - $end_state => processor.ctx.last_action == $last_action, - _ => false - }); - } - } - } - - macro_rules! test_process_binding { - { - name: $name:ident, - binding: $binding:expr, - triggers: $triggers:expr, - mode: $mode:expr, - mods: $mods:expr - } => { - #[test] - fn $name() { - if $triggers { - assert!($binding.is_triggered_by($mode, $mods, &KEY, false)); - } else { - assert!(!$binding.is_triggered_by($mode, $mods, &KEY, false)); - } - } - } - } - - test_clickstate! { - name: single_click, - initial_state: ClickState::None, - initial_button: MouseButton::Other(0), - input: Event::WindowEvent { - event: WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Left, - device_id: unsafe { ::std::mem::transmute_copy(&0) }, - modifiers: ModifiersState::default(), - }, - window_id: unsafe { ::std::mem::transmute_copy(&0) }, - }, - end_state: ClickState::Click, - last_action: MultiClick::None - } - - test_clickstate! { - name: double_click, - initial_state: ClickState::Click, - initial_button: MouseButton::Left, - input: Event::WindowEvent { - event: WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Left, - device_id: unsafe { ::std::mem::transmute_copy(&0) }, - modifiers: ModifiersState::default(), - }, - window_id: unsafe { ::std::mem::transmute_copy(&0) }, - }, - end_state: ClickState::DoubleClick, - last_action: MultiClick::DoubleClick - } - - test_clickstate! { - name: triple_click, - initial_state: ClickState::DoubleClick, - initial_button: MouseButton::Left, - input: Event::WindowEvent { - event: WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Left, - device_id: unsafe { ::std::mem::transmute_copy(&0) }, - modifiers: ModifiersState::default(), - }, - window_id: unsafe { ::std::mem::transmute_copy(&0) }, - }, - end_state: ClickState::TripleClick, - last_action: MultiClick::TripleClick - } - - test_clickstate! { - name: multi_click_separate_buttons, - initial_state: ClickState::DoubleClick, - initial_button: MouseButton::Left, - input: Event::WindowEvent { - event: WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Right, - device_id: unsafe { ::std::mem::transmute_copy(&0) }, - modifiers: ModifiersState::default(), - }, - window_id: unsafe { ::std::mem::transmute_copy(&0) }, - }, - end_state: ClickState::Click, - last_action: MultiClick::None - } - - test_process_binding! { - name: process_binding_nomode_shiftmod_require_shift, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE }, - triggers: true, - mode: TermMode::NONE, - mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_nomode_nomod_require_shift, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: true, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[1;2D"), mode: TermMode::NONE, notmode: TermMode::NONE }, - triggers: false, - mode: TermMode::NONE, - mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_nomode_controlmod, - binding: Binding { trigger: KEY, mods: ModifiersState { ctrl: true, shift: false, alt: false, logo: false }, action: Action::from("\x1b[1;5D"), mode: TermMode::NONE, notmode: TermMode::NONE }, - triggers: true, - mode: TermMode::NONE, - mods: ModifiersState { ctrl: true, shift: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_nomode_nomod_require_not_appcursor, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1b[D"), mode: TermMode::NONE, notmode: TermMode::APP_CURSOR }, - triggers: true, - mode: TermMode::NONE, - mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_appcursormode_nomod_require_appcursor, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE }, - triggers: true, - mode: TermMode::APP_CURSOR, - mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_nomode_nomod_require_appcursor, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE }, - triggers: false, - mode: TermMode::NONE, - mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_appcursormode_appkeypadmode_nomod_require_appcursor, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false }, action: Action::from("\x1bOD"), mode: TermMode::APP_CURSOR, notmode: TermMode::NONE }, - triggers: true, - mode: TermMode::APP_CURSOR | TermMode::APP_KEYPAD, - mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: false } - } - - test_process_binding! { - name: process_binding_fail_with_extra_mods, - binding: Binding { trigger: KEY, mods: ModifiersState { shift: false, ctrl: false, alt: false, logo: true }, action: Action::from("arst"), mode: TermMode::NONE, notmode: TermMode::NONE }, - triggers: false, - mode: TermMode::NONE, - mods: ModifiersState { shift: false, ctrl: false, alt: true, logo: true } - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 10c1faa6..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! Alacritty - The GPU Enhanced Terminal -#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)] -#![cfg_attr(feature = "nightly", feature(core_intrinsics))] -#![cfg_attr(all(test, feature = "bench"), feature(test))] - -#[macro_use] -extern crate log; -#[macro_use] -extern crate serde_derive; - -#[cfg(target_os = "macos")] -#[macro_use] -extern crate objc; - -#[macro_use] -pub mod macros; -pub mod ansi; -pub mod cli; -pub mod config; -mod cursor; -pub mod display; -pub mod event; -pub mod event_loop; -pub mod grid; -pub mod index; -pub mod input; -pub mod locale; -pub mod logging; -pub mod message_bar; -pub mod meter; -pub mod panic; -pub mod renderer; -pub mod selection; -pub mod sync; -pub mod term; -pub mod tty; -mod url; -pub mod util; -pub mod window; - -pub use crate::grid::Grid; -pub use crate::term::Term; - -pub mod gl { - #![allow(clippy::all)] - include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); -} diff --git a/src/locale.rs b/src/locale.rs deleted file mode 100644 index 40c915b5..00000000 --- a/src/locale.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#![allow(clippy::let_unit_value)] -#![cfg(target_os = "macos")] -use libc::{setlocale, LC_CTYPE}; -use std::env; -use std::ffi::{CStr, CString}; -use std::os::raw::c_char; -use std::ptr::null; -use std::slice; -use std::str; - -use objc::runtime::{Class, Object}; - -pub fn set_locale_environment() { - let locale_id = unsafe { - let locale_class = Class::get("NSLocale").unwrap(); - let locale: *const Object = msg_send![locale_class, currentLocale]; - let _: () = msg_send![locale_class, release]; - // `localeIdentifier` returns extra metadata with the locale (including currency and - // collator) on newer versions of macOS. This is not a valid locale, so we use - // `languageCode` and `countryCode`, if they're available (macOS 10.12+): - // https://developer.apple.com/documentation/foundation/nslocale/1416263-localeidentifier?language=objc - // https://developer.apple.com/documentation/foundation/nslocale/1643060-countrycode?language=objc - // https://developer.apple.com/documentation/foundation/nslocale/1643026-languagecode?language=objc - let is_language_code_supported: bool = - msg_send![locale, respondsToSelector: sel!(languageCode)]; - let is_country_code_supported: bool = - msg_send![locale, respondsToSelector: sel!(countryCode)]; - let locale_id = if is_language_code_supported && is_country_code_supported { - let language_code: *const Object = msg_send![locale, languageCode]; - let country_code: *const Object = msg_send![locale, countryCode]; - let language_code_str = nsstring_as_str(language_code).to_owned(); - let _: () = msg_send![language_code, release]; - let country_code_str = nsstring_as_str(country_code).to_owned(); - let _: () = msg_send![country_code, release]; - format!("{}_{}.UTF-8", &language_code_str, &country_code_str) - } else { - let identifier: *const Object = msg_send![locale, localeIdentifier]; - let identifier_str = nsstring_as_str(identifier).to_owned(); - let _: () = msg_send![identifier, release]; - identifier_str + ".UTF-8" - }; - let _: () = msg_send![locale, release]; - locale_id - }; - // check if locale_id is valid - let locale_c_str = CString::new(locale_id.to_owned()).unwrap(); - let locale_ptr = locale_c_str.as_ptr(); - let locale_id = unsafe { - // save a copy of original setting - let original = setlocale(LC_CTYPE, null()); - let saved_original = if original.is_null() { - CString::new("").unwrap() - } else { - CStr::from_ptr(original).to_owned() - }; - // try setting `locale_id` - let modified = setlocale(LC_CTYPE, locale_ptr); - let result = if modified.is_null() { String::new() } else { locale_id }; - // restore original setting - setlocale(LC_CTYPE, saved_original.as_ptr()); - result - }; - - env::set_var("LANG", &locale_id); -} - -const UTF8_ENCODING: usize = 4; - -unsafe fn nsstring_as_str<'a>(nsstring: *const Object) -> &'a str { - let cstr: *const c_char = msg_send![nsstring, UTF8String]; - let len: usize = msg_send![nsstring, lengthOfBytesUsingEncoding: UTF8_ENCODING]; - str::from_utf8(slice::from_raw_parts(cstr as *const u8, len)).unwrap() -} - -#[cfg(not(target_os = "macos"))] -pub fn set_locale_environment() {} diff --git a/src/logging.rs b/src/logging.rs deleted file mode 100644 index c6ddd8e6..00000000 --- a/src/logging.rs +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! Logging for alacritty. -//! -//! The main executable is supposed to call `initialize()` exactly once during -//! startup. All logging messages are written to stdout, given that their -//! log-level is sufficient for the level configured in `cli::Options`. -use std::env; -use std::fs::{File, OpenOptions}; -use std::io::{self, LineWriter, Stdout, Write}; -use std::path::PathBuf; -use std::process; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; - -use crossbeam_channel::Sender; -use log::{self, Level}; -use time; - -use crate::cli; -use crate::message_bar::Message; -use crate::term::color; - -const ALACRITTY_LOG_ENV: &str = "ALACRITTY_LOG"; - -pub fn initialize( - options: &cli::Options, - message_tx: Sender<Message>, -) -> Result<Option<PathBuf>, log::SetLoggerError> { - // Use env_logger if RUST_LOG environment variable is defined. Otherwise, - // use the alacritty-only logger. - if ::std::env::var("RUST_LOG").is_ok() { - ::env_logger::try_init()?; - Ok(None) - } else { - let logger = Logger::new(options.log_level, message_tx); - let path = logger.file_path(); - log::set_boxed_logger(Box::new(logger))?; - Ok(path) - } -} - -pub struct Logger { - level: log::LevelFilter, - logfile: Mutex<OnDemandLogFile>, - stdout: Mutex<LineWriter<Stdout>>, - message_tx: Sender<Message>, -} - -impl Logger { - // False positive, see: https://github.com/rust-lang-nursery/rust-clippy/issues/734 - #[allow(clippy::new_ret_no_self)] - fn new(level: log::LevelFilter, message_tx: Sender<Message>) -> Self { - log::set_max_level(level); - - let logfile = Mutex::new(OnDemandLogFile::new()); - let stdout = Mutex::new(LineWriter::new(io::stdout())); - - Logger { level, logfile, stdout, message_tx } - } - - fn file_path(&self) -> Option<PathBuf> { - if let Ok(logfile) = self.logfile.lock() { - Some(logfile.path().clone()) - } else { - None - } - } -} - -impl log::Log for Logger { - fn enabled(&self, metadata: &log::Metadata<'_>) -> bool { - metadata.level() <= self.level - } - - fn log(&self, record: &log::Record<'_>) { - if self.enabled(record.metadata()) && record.target().starts_with("alacritty") { - let now = time::strftime("%F %R", &time::now()).unwrap(); - - let msg = if record.level() >= Level::Trace { - format!( - "[{}] [{}] [{}:{}] {}\n", - now, - record.level(), - record.file().unwrap_or("?"), - record.line().map(|l| l.to_string()).unwrap_or_else(|| "?".into()), - record.args() - ) - } else { - format!("[{}] [{}] {}\n", now, record.level(), record.args()) - }; - - if let Ok(ref mut logfile) = self.logfile.lock() { - let _ = logfile.write_all(msg.as_ref()); - - if record.level() <= Level::Warn { - #[cfg(not(windows))] - let env_var = format!("${}", ALACRITTY_LOG_ENV); - #[cfg(windows)] - let env_var = format!("%{}%", ALACRITTY_LOG_ENV); - - let msg = format!( - "[{}] See log at {} ({}):\n{}", - record.level(), - logfile.path.to_string_lossy(), - env_var, - record.args(), - ); - let color = match record.level() { - Level::Error => color::RED, - Level::Warn => color::YELLOW, - _ => unreachable!(), - }; - - let mut message = Message::new(msg, color); - message.set_topic(record.file().unwrap_or("?").into()); - let _ = self.message_tx.send(message); - } - } - - if let Ok(ref mut stdout) = self.stdout.lock() { - let _ = stdout.write_all(msg.as_ref()); - } - } - } - - fn flush(&self) {} -} - -struct OnDemandLogFile { - file: Option<LineWriter<File>>, - created: Arc<AtomicBool>, - path: PathBuf, -} - -impl OnDemandLogFile { - fn new() -> Self { - let mut path = env::temp_dir(); - path.push(format!("Alacritty-{}.log", process::id())); - - // Set log path as an environment variable - env::set_var(ALACRITTY_LOG_ENV, path.as_os_str()); - - OnDemandLogFile { path, file: None, created: Arc::new(AtomicBool::new(false)) } - } - - fn file(&mut self) -> Result<&mut LineWriter<File>, io::Error> { - // Allow to recreate the file if it has been deleted at runtime - if self.file.is_some() && !self.path.as_path().exists() { - self.file = None; - } - - // Create the file if it doesn't exist yet - if self.file.is_none() { - let file = OpenOptions::new().append(true).create(true).open(&self.path); - - match file { - Ok(file) => { - self.file = Some(io::LineWriter::new(file)); - self.created.store(true, Ordering::Relaxed); - let _ = writeln!(io::stdout(), "Created log file at {:?}", self.path); - }, - Err(e) => { - let _ = writeln!(io::stdout(), "Unable to create log file: {}", e); - return Err(e); - }, - } - } - - Ok(self.file.as_mut().unwrap()) - } - - fn path(&self) -> &PathBuf { - &self.path - } -} - -impl Write for OnDemandLogFile { - fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> { - self.file()?.write(buf) - } - - fn flush(&mut self) -> Result<(), io::Error> { - self.file()?.flush() - } -} diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index 519f8b6a..00000000 --- a/src/macros.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#[macro_export] -macro_rules! die { - ($($arg:tt)*) => {{ - error!($($arg)*); - ::std::process::exit(1); - }} -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index d7e335e2..00000000 --- a/src/main.rs +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! Alacritty - The GPU Enhanced Terminal -#![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use, clippy::wrong_pub_self_convention)] -#![cfg_attr(feature = "nightly", feature(core_intrinsics))] -#![cfg_attr(all(test, feature = "bench"), feature(test))] -// With the default subsystem, 'console', windows creates an additional console -// window for the program. -// This is silently ignored on non-windows systems. -// See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details. -#![windows_subsystem = "windows"] - -#[cfg(target_os = "macos")] -use dirs; - -#[cfg(windows)] -use winapi::um::wincon::{AttachConsole, FreeConsole, ATTACH_PARENT_PROCESS}; - -use log::{error, info}; - -use std::error::Error; -use std::fs; -use std::io::{self, Write}; -use std::sync::Arc; - -#[cfg(target_os = "macos")] -use std::env; - -#[cfg(not(windows))] -use std::os::unix::io::AsRawFd; - -use alacritty::config::{self, Config, Monitor}; -use alacritty::display::Display; -use alacritty::event_loop::{self, EventLoop, Msg}; -#[cfg(target_os = "macos")] -use alacritty::locale; -use alacritty::logging; -use alacritty::message_bar::MessageBuffer; -use alacritty::panic; -use alacritty::sync::FairMutex; -use alacritty::term::Term; -use alacritty::tty; -use alacritty::util::fmt::Red; -use alacritty::{cli, die, event}; - -fn main() { - panic::attach_handler(); - - // When linked with the windows subsystem windows won't automatically attach - // to the console of the parent process, so we do it explicitly. This fails - // silently if the parent has no console. - #[cfg(windows)] - unsafe { - AttachConsole(ATTACH_PARENT_PROCESS); - } - - // Load command line options - let options = cli::Options::load(); - - // Setup storage for message UI - let message_buffer = MessageBuffer::new(); - - // Initialize the logger as soon as possible as to capture output from other subsystems - let log_file = - logging::initialize(&options, message_buffer.tx()).expect("Unable to initialize logger"); - - // Load configuration file - // If the file is a command line argument, we won't write a generated default file - let config_path = options - .config_path() - .or_else(Config::installed_config) - .or_else(|| Config::write_defaults().ok()) - .map(|path| path.to_path_buf()); - let config = if let Some(path) = config_path { - Config::load_from(path).update_dynamic_title(&options) - } else { - error!("Unable to write the default config"); - Config::default() - }; - - // Switch to home directory - #[cfg(target_os = "macos")] - env::set_current_dir(dirs::home_dir().unwrap()).unwrap(); - // Set locale - #[cfg(target_os = "macos")] - locale::set_locale_environment(); - - // Store if log file should be deleted before moving config - let persistent_logging = options.persistent_logging || config.persistent_logging(); - - // Run alacritty - if let Err(err) = run(config, &options, message_buffer) { - die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", Red(err)); - } - - // Clean up logfile - if let Some(log_file) = log_file { - if !persistent_logging && fs::remove_file(&log_file).is_ok() { - let _ = writeln!(io::stdout(), "Deleted log file at {:?}", log_file); - } - } -} - -/// Run Alacritty -/// -/// Creates a window, the terminal state, pty, I/O event loop, input processor, -/// config change monitor, and runs the main display loop. -fn run( - mut config: Config, - options: &cli::Options, - message_buffer: MessageBuffer, -) -> Result<(), Box<dyn Error>> { - info!("Welcome to Alacritty"); - if let Some(config_path) = config.path() { - info!("Configuration loaded from {:?}", config_path.display()); - }; - - // Set environment variables - tty::setup_env(&config); - - // Create a display. - // - // The display manages a window and can draw the terminal - let mut display = Display::new(&config, options)?; - - info!("PTY Dimensions: {:?} x {:?}", display.size().lines(), display.size().cols()); - - // Create the terminal - // - // This object contains all of the state about what's being displayed. It's - // wrapped in a clonable mutex since both the I/O loop and display need to - // access it. - let terminal = Term::new(&config, display.size().to_owned(), message_buffer); - let terminal = Arc::new(FairMutex::new(terminal)); - - // Find the window ID for setting $WINDOWID - let window_id = display.get_window_id(); - - // Create the pty - // - // The pty forks a process to run the shell on the slave side of the - // pseudoterminal. A file descriptor for the master side is retained for - // reading/writing to the shell. - let pty = tty::new(&config, options, &display.size(), window_id); - - // Get a reference to something that we can resize - // - // This exists because rust doesn't know the interface is thread-safe - // and we need to be able to resize the PTY from the main thread while the IO - // thread owns the EventedRW object. - #[cfg(windows)] - let mut resize_handle = pty.resize_handle(); - #[cfg(not(windows))] - let mut resize_handle = pty.fd.as_raw_fd(); - - // Create the pseudoterminal I/O loop - // - // pty I/O is ran on another thread as to not occupy cycles used by the - // renderer and input processing. Note that access to the terminal state is - // synchronized since the I/O loop updates the state, and the display - // consumes it periodically. - let event_loop = - EventLoop::new(Arc::clone(&terminal), display.notifier(), pty, options.ref_test); - - // The event loop channel allows write requests from the event processor - // to be sent to the loop and ultimately written to the pty. - let loop_tx = event_loop.channel(); - - // Event processor - // - // Need the Rc<RefCell<_>> here since a ref is shared in the resize callback - let mut processor = event::Processor::new( - event_loop::Notifier(event_loop.channel()), - display.resize_channel(), - options, - &config, - options.ref_test, - display.size().to_owned(), - ); - - // Create a config monitor when config was loaded from path - // - // The monitor watches the config file for changes and reloads it. Pending - // config changes are processed in the main loop. - let config_monitor = match (options.live_config_reload, config.live_config_reload()) { - // Start monitor if CLI flag says yes - (Some(true), _) | - // Or if no CLI flag was passed and the config says yes - (None, true) => config.path() - .map(|path| config::Monitor::new(path, display.notifier())), - // Otherwise, don't start the monitor - _ => None, - }; - - // Kick off the I/O thread - let _io_thread = event_loop.spawn(None); - - info!("Initialisation complete"); - - // Main display loop - loop { - // Process input and window events - let mut terminal_lock = processor.process_events(&terminal, display.window()); - - // Handle config reloads - if let Some(ref path) = config_monitor.as_ref().and_then(Monitor::pending) { - // Clear old config messages from bar - terminal_lock.message_buffer_mut().remove_topic(config::SOURCE_FILE_PATH); - - if let Ok(new_config) = Config::reload_from(path) { - config = new_config.update_dynamic_title(options); - display.update_config(&config); - processor.update_config(&config); - terminal_lock.update_config(&config); - } - - terminal_lock.dirty = true; - } - - // Begin shutdown if the flag was raised - if terminal_lock.should_exit() || tty::process_should_exit() { - break; - } - - // Maybe draw the terminal - if terminal_lock.needs_draw() { - // Try to update the position of the input method editor - #[cfg(not(windows))] - display.update_ime_position(&terminal_lock); - - // Handle pending resize events - // - // The second argument is a list of types that want to be notified - // of display size changes. - display.handle_resize(&mut terminal_lock, &config, &mut resize_handle, &mut processor); - - drop(terminal_lock); - - // Draw the current state of the terminal - display.draw(&terminal, &config); - } - } - - loop_tx.send(Msg::Shutdown).expect("Error sending shutdown to event loop"); - - // FIXME patch notify library to have a shutdown method - // config_reloader.join().ok(); - - // Without explicitly detaching the console cmd won't redraw it's prompt - #[cfg(windows)] - unsafe { - FreeConsole(); - } - - info!("Goodbye"); - - Ok(()) -} diff --git a/src/message_bar.rs b/src/message_bar.rs deleted file mode 100644 index 8883dcb0..00000000 --- a/src/message_bar.rs +++ /dev/null @@ -1,473 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crossbeam_channel::{Receiver, Sender}; - -use crate::term::color::Rgb; -use crate::term::SizeInfo; - -pub const CLOSE_BUTTON_TEXT: &str = "[X]"; -const CLOSE_BUTTON_PADDING: usize = 1; -const MIN_FREE_LINES: usize = 3; -const TRUNCATED_MESSAGE: &str = "[MESSAGE TRUNCATED]"; - -/// Message for display in the MessageBuffer -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct Message { - text: String, - color: Rgb, - topic: Option<String>, -} - -impl Message { - /// Create a new message - pub fn new(text: String, color: Rgb) -> Message { - Message { text, color, topic: None } - } - - /// Formatted message text lines - pub fn text(&self, size_info: &SizeInfo) -> Vec<String> { - let num_cols = size_info.cols().0; - let max_lines = size_info.lines().saturating_sub(MIN_FREE_LINES); - let button_len = CLOSE_BUTTON_TEXT.len(); - - // Split line to fit the screen - let mut lines = Vec::new(); - let mut line = String::new(); - for c in self.text.trim().chars() { - if c == '\n' - || line.len() == num_cols - // Keep space in first line for button - || (lines.is_empty() - && num_cols >= button_len - && line.len() == num_cols.saturating_sub(button_len + CLOSE_BUTTON_PADDING)) - { - // Attempt to wrap on word boundaries - if let (Some(index), true) = (line.rfind(char::is_whitespace), c != '\n') { - let split = line.split_off(index + 1); - line.pop(); - lines.push(Self::pad_text(line, num_cols)); - line = split - } else { - lines.push(Self::pad_text(line, num_cols)); - line = String::new(); - } - } - - if c != '\n' { - line.push(c); - } - } - lines.push(Self::pad_text(line, num_cols)); - - // Truncate output if it's too long - if lines.len() > max_lines { - lines.truncate(max_lines); - if TRUNCATED_MESSAGE.len() <= num_cols { - if let Some(line) = lines.iter_mut().last() { - *line = Self::pad_text(TRUNCATED_MESSAGE.into(), num_cols); - } - } - } - - // Append close button to first line - if button_len <= num_cols { - if let Some(line) = lines.get_mut(0) { - line.truncate(num_cols - button_len); - line.push_str(CLOSE_BUTTON_TEXT); - } - } - - lines - } - - /// Message color - #[inline] - pub fn color(&self) -> Rgb { - self.color - } - - /// Message topic - #[inline] - pub fn topic(&self) -> Option<&String> { - self.topic.as_ref() - } - - /// Update the message topic - #[inline] - pub fn set_topic(&mut self, topic: String) { - self.topic = Some(topic); - } - - /// Right-pad text to fit a specific number of columns - #[inline] - fn pad_text(mut text: String, num_cols: usize) -> String { - let padding_len = num_cols.saturating_sub(text.len()); - text.extend(vec![' '; padding_len]); - text - } -} - -/// Storage for message bar -#[derive(Debug)] -pub struct MessageBuffer { - current: Option<Message>, - messages: Receiver<Message>, - tx: Sender<Message>, -} - -impl MessageBuffer { - /// Create new message buffer - pub fn new() -> MessageBuffer { - let (tx, messages) = crossbeam_channel::unbounded(); - MessageBuffer { current: None, messages, tx } - } - - /// Check if there are any messages queued - #[inline] - pub fn is_empty(&self) -> bool { - self.current.is_none() - } - - /// Current message - #[inline] - pub fn message(&mut self) -> Option<Message> { - if let Some(current) = &self.current { - Some(current.clone()) - } else { - self.current = self.messages.try_recv().ok(); - self.current.clone() - } - } - - /// Channel for adding new messages - #[inline] - pub fn tx(&self) -> Sender<Message> { - self.tx.clone() - } - - /// Remove the currently visible message - #[inline] - pub fn pop(&mut self) { - // Remove all duplicates - for msg in self - .messages - .try_iter() - .take(self.messages.len()) - .filter(|m| Some(m) != self.current.as_ref()) - { - let _ = self.tx.send(msg); - } - - // Remove the message itself - self.current = self.messages.try_recv().ok(); - } - - /// Remove all messages with a specific topic - #[inline] - pub fn remove_topic(&mut self, topic: &str) { - // Filter messages currently pending - for msg in self - .messages - .try_iter() - .take(self.messages.len()) - .filter(|m| m.topic().map(String::as_str) != Some(topic)) - { - let _ = self.tx.send(msg); - } - - // Remove the currently active message - self.current = self.messages.try_recv().ok(); - } -} - -impl Default for MessageBuffer { - fn default() -> MessageBuffer { - MessageBuffer::new() - } -} - -#[cfg(test)] -mod test { - use super::{Message, MessageBuffer, MIN_FREE_LINES}; - use crate::term::{color, SizeInfo}; - - #[test] - fn appends_close_button() { - let input = "a"; - let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); - let size = SizeInfo { - width: 7., - height: 10., - cell_width: 1., - cell_height: 1., - padding_x: 0., - padding_y: 0., - dpr: 0., - }; - - let lines = message_buffer.message().unwrap().text(&size); - - assert_eq!(lines, vec![String::from("a [X]")]); - } - - #[test] - fn multiline_close_button_first_line() { - let input = "fo\nbar"; - let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); - let size = SizeInfo { - width: 6., - height: 10., - cell_width: 1., - cell_height: 1., - padding_x: 0., - padding_y: 0., - dpr: 0., - }; - - let lines = message_buffer.message().unwrap().text(&size); - - assert_eq!(lines, vec![String::from("fo [X]"), String::from("bar ")]); - } - - #[test] - fn splits_on_newline() { - let input = "a\nb"; - let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); - let size = SizeInfo { - width: 6., - height: 10., - cell_width: 1., - cell_height: 1., - padding_x: 0., - padding_y: 0., - dpr: 0., - }; - - let lines = message_buffer.message().unwrap().text(&size); - - assert_eq!(lines.len(), 2); - } - - #[test] - fn splits_on_length() { - let input = "foobar1"; - let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); - let size = SizeInfo { - width: 6., - height: 10., - cell_width: 1., - cell_height: 1., - padding_x: 0., - padding_y: 0., - dpr: 0., - }; - - let lines = message_buffer.message().unwrap().text(&size); - - assert_eq!(lines.len(), 2); - } - - #[test] - fn empty_with_shortterm() { - let input = "foobar"; - let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); - let size = SizeInfo { - width: 6., - height: 0., - cell_width: 1., - cell_height: 1., - padding_x: 0., - padding_y: 0., - dpr: 0., - }; - - let lines = message_buffer.message().unwrap().text(&size); - - assert_eq!(lines.len(), 0); - } - - #[test] - fn truncates_long_messages() { - let input = "hahahahahahahahahahaha truncate this because it's too long for the term"; - let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); - let size = SizeInfo { - width: 22., - height: (MIN_FREE_LINES + 2) as f32, - cell_width: 1., - cell_height: 1., - padding_x: 0., - padding_y: 0., - dpr: 0., - }; - - let lines = message_buffer.message().unwrap().text(&size); - - assert_eq!(lines, vec![ - String::from("hahahahahahahahaha [X]"), - String::from("[MESSAGE TRUNCATED] ") - ]); - } - - #[test] - fn hide_button_when_too_narrow() { - let input = "ha"; - let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); - let size = SizeInfo { - width: 2., - height: 10., - cell_width: 1., - cell_height: 1., - padding_x: 0., - padding_y: 0., - dpr: 0., - }; - - let lines = message_buffer.message().unwrap().text(&size); - - assert_eq!(lines, vec![String::from("ha")]); - } - - #[test] - fn hide_truncated_when_too_narrow() { - let input = "hahahahahahahahaha"; - let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); - let size = SizeInfo { - width: 2., - height: (MIN_FREE_LINES + 2) as f32, - cell_width: 1., - cell_height: 1., - padding_x: 0., - padding_y: 0., - dpr: 0., - }; - - let lines = message_buffer.message().unwrap().text(&size); - - assert_eq!(lines, vec![String::from("ha"), String::from("ha")]); - } - - #[test] - fn add_newline_for_button() { - let input = "test"; - let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); - let size = SizeInfo { - width: 5., - height: 10., - cell_width: 1., - cell_height: 1., - padding_x: 0., - padding_y: 0., - dpr: 0., - }; - - let lines = message_buffer.message().unwrap().text(&size); - - assert_eq!(lines, vec![String::from("t [X]"), String::from("est ")]); - } - - #[test] - fn remove_topic() { - let mut message_buffer = MessageBuffer::new(); - for i in 0..10 { - let mut msg = Message::new(i.to_string(), color::RED); - if i % 2 == 0 && i < 5 { - msg.set_topic("topic".into()); - } - message_buffer.tx().send(msg).unwrap(); - } - - message_buffer.remove_topic("topic"); - - // Count number of messages - let mut num_messages = 0; - while message_buffer.message().is_some() { - num_messages += 1; - message_buffer.pop(); - } - - assert_eq!(num_messages, 7); - } - - #[test] - fn pop() { - let mut message_buffer = MessageBuffer::new(); - let one = Message::new(String::from("one"), color::RED); - message_buffer.tx().send(one.clone()).unwrap(); - let two = Message::new(String::from("two"), color::YELLOW); - message_buffer.tx().send(two.clone()).unwrap(); - - assert_eq!(message_buffer.message(), Some(one)); - - message_buffer.pop(); - - assert_eq!(message_buffer.message(), Some(two)); - } - - #[test] - fn wrap_on_words() { - let input = "a\nbc defg"; - let mut message_buffer = MessageBuffer::new(); - message_buffer.tx().send(Message::new(input.into(), color::RED)).unwrap(); - let size = SizeInfo { - width: 5., - height: 10., - cell_width: 1., - cell_height: 1., - padding_x: 0., - padding_y: 0., - dpr: 0., - }; - - let lines = message_buffer.message().unwrap().text(&size); - - assert_eq!(lines, vec![ - String::from("a [X]"), - String::from("bc "), - String::from("defg ") - ]); - } - - #[test] - fn remove_duplicates() { - let mut message_buffer = MessageBuffer::new(); - for _ in 0..10 { - let msg = Message::new(String::from("test"), color::RED); - message_buffer.tx().send(msg).unwrap(); - } - message_buffer.tx().send(Message::new(String::from("other"), color::RED)).unwrap(); - message_buffer.tx().send(Message::new(String::from("test"), color::YELLOW)).unwrap(); - let _ = message_buffer.message(); - - message_buffer.pop(); - - // Count number of messages - let mut num_messages = 0; - while message_buffer.message().is_some() { - num_messages += 1; - message_buffer.pop(); - } - - assert_eq!(num_messages, 2); - } -} diff --git a/src/meter.rs b/src/meter.rs deleted file mode 100644 index 81da4442..00000000 --- a/src/meter.rs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! Rendering time meter -//! -//! Used to track rendering times and provide moving averages. -//! -//! # Examples -//! -//! ```rust -//! // create a meter -//! let mut meter = alacritty::meter::Meter::new(); -//! -//! // Sample something. -//! { -//! let _sampler = meter.sampler(); -//! } -//! -//! // Get the moving average. The meter tracks a fixed number of samples, and -//! // the average won't mean much until it's filled up at least once. -//! println!("Average time: {}", meter.average()); - -use std::time::{Duration, Instant}; - -const NUM_SAMPLES: usize = 10; - -/// The meter -#[derive(Default)] -pub struct Meter { - /// Track last 60 timestamps - times: [f64; NUM_SAMPLES], - - /// Average sample time in microseconds - avg: f64, - - /// Index of next time to update. - index: usize, -} - -/// Sampler -/// -/// Samplers record how long they are "alive" for and update the meter on drop. -pub struct Sampler<'a> { - /// Reference to meter that created the sampler - meter: &'a mut Meter, - - // When the sampler was created - created_at: Instant, -} - -impl<'a> Sampler<'a> { - fn new(meter: &'a mut Meter) -> Sampler<'a> { - Sampler { meter, created_at: Instant::now() } - } - - #[inline] - fn alive_duration(&self) -> Duration { - self.created_at.elapsed() - } -} - -impl<'a> Drop for Sampler<'a> { - fn drop(&mut self) { - self.meter.add_sample(self.alive_duration()); - } -} - -impl Meter { - /// Create a meter - pub fn new() -> Meter { - Default::default() - } - - /// Get a sampler - pub fn sampler(&mut self) -> Sampler<'_> { - Sampler::new(self) - } - - /// Get the current average sample duration in microseconds - pub fn average(&self) -> f64 { - self.avg - } - - /// Add a sample - /// - /// Used by Sampler::drop. - fn add_sample(&mut self, sample: Duration) { - let mut usec = 0f64; - - usec += f64::from(sample.subsec_nanos()) / 1e3; - usec += (sample.as_secs() as f64) * 1e6; - - let prev = self.times[self.index]; - self.times[self.index] = usec; - self.avg -= prev / NUM_SAMPLES as f64; - self.avg += usec / NUM_SAMPLES as f64; - self.index = (self.index + 1) % NUM_SAMPLES; - } -} diff --git a/src/panic.rs b/src/panic.rs deleted file mode 100644 index 4d3524ed..00000000 --- a/src/panic.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! ANSI Terminal Stream Parsing - -// Use the default behavior of the other platforms. -#[cfg(not(windows))] -pub fn attach_handler() {} - -// Install a panic handler that renders the panic in a classical Windows error -// dialog box as well as writes the panic to STDERR. -#[cfg(windows)] -pub fn attach_handler() { - use std::{io, io::Write, panic, ptr}; - use winapi::um::winuser; - - panic::set_hook(Box::new(|panic_info| { - let _ = writeln!(io::stderr(), "{}", panic_info); - let msg = format!("{}\n\nPress Ctrl-C to Copy", panic_info); - unsafe { - winuser::MessageBoxW( - ptr::null_mut(), - win32_string(&msg).as_ptr(), - win32_string("Alacritty: Runtime Error").as_ptr(), - winuser::MB_ICONERROR - | winuser::MB_OK - | winuser::MB_SETFOREGROUND - | winuser::MB_TASKMODAL, - ); - } - })); -} - -// Converts the string slice into a Windows-standard representation for "W"- -// suffixed function variants, which accept UTF-16 encoded string values. -#[cfg(windows)] -fn win32_string(value: &str) -> Vec<u16> { - use std::ffi::OsStr; - use std::iter::once; - use std::os::windows::ffi::OsStrExt; - OsStr::new(value).encode_wide().chain(once(0)).collect() -} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs deleted file mode 100644 index c0e3081d..00000000 --- a/src/renderer/mod.rs +++ /dev/null @@ -1,1625 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use std::collections::HashMap; -use std::fs::File; -use std::hash::BuildHasherDefault; -use std::io::{self, Read}; -use std::mem::size_of; -use std::path::PathBuf; -use std::ptr; -use std::sync::mpsc; -use std::time::Duration; - -use fnv::FnvHasher; -use font::{self, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer}; -use glutin::dpi::PhysicalSize; -use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; - -use crate::ansi::CursorStyle; -use crate::config::{self, Config, Delta}; -use crate::gl; -use crate::gl::types::*; -use crate::index::{Column, Line}; -use crate::renderer::rects::{Rect, Rects}; -use crate::term::color::Rgb; -use crate::term::{self, cell, RenderableCell, RenderableCellContent}; - -pub mod rects; - -// Shader paths for live reload -static TEXT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl"); -static TEXT_SHADER_V_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl"); -static RECT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.f.glsl"); -static RECT_SHADER_V_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.v.glsl"); - -// Shader source which is used when live-shader-reload feature is disable -static TEXT_SHADER_F: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl")); -static TEXT_SHADER_V: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl")); -static RECT_SHADER_F: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.f.glsl")); -static RECT_SHADER_V: &'static str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.v.glsl")); - -/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory -pub trait LoadGlyph { - /// Load the rasterized glyph into GPU memory - fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph; - - /// Clear any state accumulated from previous loaded glyphs - /// - /// This can, for instance, be used to reset the texture Atlas. - fn clear(&mut self); -} - -enum Msg { - ShaderReload, -} - -#[derive(Debug)] -pub enum Error { - ShaderCreation(ShaderCreationError), -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - Error::ShaderCreation(ref err) => Some(err), - } - } - - fn description(&self) -> &str { - match *self { - Error::ShaderCreation(ref err) => err.description(), - } - } -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - Error::ShaderCreation(ref err) => { - write!(f, "There was an error initializing the shaders: {}", err) - }, - } - } -} - -impl From<ShaderCreationError> for Error { - fn from(val: ShaderCreationError) -> Error { - Error::ShaderCreation(val) - } -} - -/// Text drawing program -/// -/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a". -#[derive(Debug)] -pub struct TextShaderProgram { - // Program id - id: GLuint, - - /// projection scale and offset uniform - u_projection: GLint, - - /// Cell dimensions (pixels) - u_cell_dim: GLint, - - /// Background pass flag - /// - /// Rendering is split into two passes; 1 for backgrounds, and one for text - u_background: GLint, -} - -/// Rectangle drawing program -/// -/// Uniforms are prefixed with "u" -#[derive(Debug)] -pub struct RectShaderProgram { - // Program id - id: GLuint, - /// Rectangle color - u_color: GLint, -} - -#[derive(Copy, Debug, Clone)] -pub struct Glyph { - tex_id: GLuint, - top: f32, - left: f32, - width: f32, - height: f32, - uv_bot: f32, - uv_left: f32, - uv_width: f32, - uv_height: f32, -} - -/// Naïve glyph cache -/// -/// Currently only keyed by `char`, and thus not possible to hold different -/// representations of the same code point. -pub struct GlyphCache { - /// Cache of buffered glyphs - cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>, - - /// Cache of buffered cursor glyphs - cursor_cache: HashMap<CursorStyle, Glyph, BuildHasherDefault<FnvHasher>>, - - /// Rasterizer for loading new glyphs - rasterizer: Rasterizer, - - /// regular font - font_key: FontKey, - - /// italic font - italic_key: FontKey, - - /// bold font - bold_key: FontKey, - - /// font size - font_size: font::Size, - - /// glyph offset - glyph_offset: Delta<i8>, - - metrics: ::font::Metrics, -} - -impl GlyphCache { - pub fn new<L>( - mut rasterizer: Rasterizer, - font: &config::Font, - loader: &mut L, - ) -> Result<GlyphCache, font::Error> - where - L: LoadGlyph, - { - let (regular, bold, italic) = Self::compute_font_keys(font, &mut rasterizer)?; - - // Need to load at least one glyph for the face before calling metrics. - // The glyph requested here ('m' at the time of writing) has no special - // meaning. - rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; - - let metrics = rasterizer.metrics(regular, font.size())?; - - let mut cache = GlyphCache { - cache: HashMap::default(), - cursor_cache: HashMap::default(), - rasterizer, - font_size: font.size(), - font_key: regular, - bold_key: bold, - italic_key: italic, - glyph_offset: *font.glyph_offset(), - metrics, - }; - - cache.load_glyphs_for_font(regular, loader); - cache.load_glyphs_for_font(bold, loader); - cache.load_glyphs_for_font(italic, loader); - - Ok(cache) - } - - fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) { - let size = self.font_size; - for i in 32u8..=128u8 { - self.get(GlyphKey { font_key: font, c: i as char, size }, loader); - } - } - - /// Computes font keys for (Regular, Bold, Italic) - fn compute_font_keys( - font: &config::Font, - rasterizer: &mut Rasterizer, - ) -> Result<(FontKey, FontKey, FontKey), font::Error> { - let size = font.size(); - - // Load regular font - let regular_desc = - Self::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal); - - let regular = rasterizer.load_font(®ular_desc, size)?; - - // helper to load a description if it is not the regular_desc - let mut load_or_regular = |desc: FontDesc| { - if desc == regular_desc { - regular - } else { - rasterizer.load_font(&desc, size).unwrap_or_else(|_| regular) - } - }; - - // Load bold font - let bold_desc = Self::make_desc(&font.bold(), font::Slant::Normal, font::Weight::Bold); - - let bold = load_or_regular(bold_desc); - - // Load italic font - let italic_desc = - Self::make_desc(&font.italic(), font::Slant::Italic, font::Weight::Normal); - - let italic = load_or_regular(italic_desc); - - Ok((regular, bold, italic)) - } - - fn make_desc( - desc: &config::FontDescription, - slant: font::Slant, - weight: font::Weight, - ) -> FontDesc { - let style = if let Some(ref spec) = desc.style { - font::Style::Specific(spec.to_owned()) - } else { - font::Style::Description { slant, weight } - }; - FontDesc::new(desc.family.clone(), style) - } - - pub fn font_metrics(&self) -> font::Metrics { - self.rasterizer - .metrics(self.font_key, self.font_size) - .expect("metrics load since font is loaded at glyph cache creation") - } - - pub fn get<'a, L>(&'a mut self, glyph_key: GlyphKey, loader: &mut L) -> &'a Glyph - where - L: LoadGlyph, - { - let glyph_offset = self.glyph_offset; - let rasterizer = &mut self.rasterizer; - let metrics = &self.metrics; - self.cache.entry(glyph_key).or_insert_with(|| { - let mut rasterized = - rasterizer.get_glyph(glyph_key).unwrap_or_else(|_| Default::default()); - - rasterized.left += i32::from(glyph_offset.x); - rasterized.top += i32::from(glyph_offset.y); - rasterized.top -= metrics.descent as i32; - - loader.load_glyph(&rasterized) - }) - } - - pub fn update_font_size<L: LoadGlyph>( - &mut self, - font: &config::Font, - size: font::Size, - dpr: f64, - loader: &mut L, - ) -> Result<(), font::Error> { - // Clear currently cached data in both GL and the registry - loader.clear(); - self.cache = HashMap::default(); - self.cursor_cache = HashMap::default(); - - // Update dpi scaling - self.rasterizer.update_dpr(dpr as f32); - - // Recompute font keys - let font = font.to_owned().with_size(size); - let (regular, bold, italic) = Self::compute_font_keys(&font, &mut self.rasterizer)?; - - self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; - let metrics = self.rasterizer.metrics(regular, size)?; - - info!("Font size changed to {:?} with DPR of {}", font.size, dpr); - - self.font_size = font.size; - self.font_key = regular; - self.bold_key = bold; - self.italic_key = italic; - self.metrics = metrics; - - self.load_glyphs_for_font(regular, loader); - self.load_glyphs_for_font(bold, loader); - self.load_glyphs_for_font(italic, loader); - - Ok(()) - } - - // Calculate font metrics without access to a glyph cache - // - // This should only be used *before* OpenGL is initialized and the glyph cache can be filled. - pub fn static_metrics(config: &Config, dpr: f32) -> Result<font::Metrics, font::Error> { - let font = config.font().clone(); - - let mut rasterizer = font::Rasterizer::new(dpr, config.use_thin_strokes())?; - let regular_desc = - GlyphCache::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal); - let regular = rasterizer.load_font(®ular_desc, font.size())?; - rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; - - rasterizer.metrics(regular, font.size()) - } -} - -#[derive(Debug)] -#[repr(C)] -struct InstanceData { - // coords - col: f32, - row: f32, - // glyph offset - left: f32, - top: f32, - // glyph scale - width: f32, - height: f32, - // uv offset - uv_left: f32, - uv_bot: f32, - // uv scale - uv_width: f32, - uv_height: f32, - // color - r: f32, - g: f32, - b: f32, - // background color - bg_r: f32, - bg_g: f32, - bg_b: f32, - bg_a: f32, -} - -#[derive(Debug)] -pub struct QuadRenderer { - program: TextShaderProgram, - rect_program: RectShaderProgram, - vao: GLuint, - ebo: GLuint, - vbo_instance: GLuint, - rect_vao: GLuint, - rect_vbo: GLuint, - atlas: Vec<Atlas>, - current_atlas: usize, - active_tex: GLuint, - batch: Batch, - rx: mpsc::Receiver<Msg>, -} - -#[derive(Debug)] -pub struct RenderApi<'a> { - active_tex: &'a mut GLuint, - batch: &'a mut Batch, - atlas: &'a mut Vec<Atlas>, - current_atlas: &'a mut usize, - program: &'a mut TextShaderProgram, - config: &'a Config, -} - -#[derive(Debug)] -pub struct LoaderApi<'a> { - active_tex: &'a mut GLuint, - atlas: &'a mut Vec<Atlas>, - current_atlas: &'a mut usize, -} - -#[derive(Debug)] -pub struct PackedVertex { - x: f32, - y: f32, -} - -#[derive(Debug, Default)] -pub struct Batch { - tex: GLuint, - instances: Vec<InstanceData>, -} - -impl Batch { - #[inline] - pub fn new() -> Batch { - Batch { tex: 0, instances: Vec::with_capacity(BATCH_MAX) } - } - - pub fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph) { - if self.is_empty() { - self.tex = glyph.tex_id; - } - - self.instances.push(InstanceData { - col: cell.column.0 as f32, - row: cell.line.0 as f32, - - top: glyph.top, - left: glyph.left, - width: glyph.width, - height: glyph.height, - - uv_bot: glyph.uv_bot, - uv_left: glyph.uv_left, - uv_width: glyph.uv_width, - uv_height: glyph.uv_height, - - r: f32::from(cell.fg.r), - g: f32::from(cell.fg.g), - b: f32::from(cell.fg.b), - - bg_r: f32::from(cell.bg.r), - bg_g: f32::from(cell.bg.g), - bg_b: f32::from(cell.bg.b), - bg_a: cell.bg_alpha, - }); - } - - #[inline] - pub fn full(&self) -> bool { - self.capacity() == self.len() - } - - #[inline] - pub fn len(&self) -> usize { - self.instances.len() - } - - #[inline] - pub fn capacity(&self) -> usize { - BATCH_MAX - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - #[inline] - pub fn size(&self) -> usize { - self.len() * size_of::<InstanceData>() - } - - pub fn clear(&mut self) { - self.tex = 0; - self.instances.clear(); - } -} - -/// Maximum items to be drawn in a batch. -const BATCH_MAX: usize = 0x1_0000; -const ATLAS_SIZE: i32 = 1024; - -impl QuadRenderer { - pub fn new() -> Result<QuadRenderer, Error> { - let program = TextShaderProgram::new()?; - let rect_program = RectShaderProgram::new()?; - - let mut vao: GLuint = 0; - let mut ebo: GLuint = 0; - - let mut vbo_instance: GLuint = 0; - - let mut rect_vao: GLuint = 0; - let mut rect_vbo: GLuint = 0; - let mut rect_ebo: GLuint = 0; - - unsafe { - gl::Enable(gl::BLEND); - gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); - gl::Enable(gl::MULTISAMPLE); - - // Disable depth mask, as the renderer never uses depth tests - gl::DepthMask(gl::FALSE); - - gl::GenVertexArrays(1, &mut vao); - gl::GenBuffers(1, &mut ebo); - gl::GenBuffers(1, &mut vbo_instance); - gl::BindVertexArray(vao); - - // --------------------- - // Set up element buffer - // --------------------- - let indices: [u32; 6] = [0, 1, 3, 1, 2, 3]; - - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo); - gl::BufferData( - gl::ELEMENT_ARRAY_BUFFER, - (6 * size_of::<u32>()) as isize, - indices.as_ptr() as *const _, - gl::STATIC_DRAW, - ); - - // ---------------------------- - // Setup vertex instance buffer - // ---------------------------- - gl::BindBuffer(gl::ARRAY_BUFFER, vbo_instance); - gl::BufferData( - gl::ARRAY_BUFFER, - (BATCH_MAX * size_of::<InstanceData>()) as isize, - ptr::null(), - gl::STREAM_DRAW, - ); - // coords - gl::VertexAttribPointer( - 0, - 2, - gl::FLOAT, - gl::FALSE, - size_of::<InstanceData>() as i32, - ptr::null(), - ); - gl::EnableVertexAttribArray(0); - gl::VertexAttribDivisor(0, 1); - // glyphoffset - gl::VertexAttribPointer( - 1, - 4, - gl::FLOAT, - gl::FALSE, - size_of::<InstanceData>() as i32, - (2 * size_of::<f32>()) as *const _, - ); - gl::EnableVertexAttribArray(1); - gl::VertexAttribDivisor(1, 1); - // uv - gl::VertexAttribPointer( - 2, - 4, - gl::FLOAT, - gl::FALSE, - size_of::<InstanceData>() as i32, - (6 * size_of::<f32>()) as *const _, - ); - gl::EnableVertexAttribArray(2); - gl::VertexAttribDivisor(2, 1); - // color - gl::VertexAttribPointer( - 3, - 3, - gl::FLOAT, - gl::FALSE, - size_of::<InstanceData>() as i32, - (10 * size_of::<f32>()) as *const _, - ); - gl::EnableVertexAttribArray(3); - gl::VertexAttribDivisor(3, 1); - // color - gl::VertexAttribPointer( - 4, - 4, - gl::FLOAT, - gl::FALSE, - size_of::<InstanceData>() as i32, - (13 * size_of::<f32>()) as *const _, - ); - gl::EnableVertexAttribArray(4); - gl::VertexAttribDivisor(4, 1); - - // Rectangle setup - gl::GenVertexArrays(1, &mut rect_vao); - gl::GenBuffers(1, &mut rect_vbo); - gl::GenBuffers(1, &mut rect_ebo); - gl::BindVertexArray(rect_vao); - let indices: [i32; 6] = [0, 1, 3, 1, 2, 3]; - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, rect_ebo); - gl::BufferData( - gl::ELEMENT_ARRAY_BUFFER, - (size_of::<i32>() * indices.len()) as _, - indices.as_ptr() as *const _, - gl::STATIC_DRAW, - ); - - // Cleanup - gl::BindVertexArray(0); - gl::BindBuffer(gl::ARRAY_BUFFER, 0); - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); - } - - let (msg_tx, msg_rx) = mpsc::channel(); - - if cfg!(feature = "live-shader-reload") { - ::std::thread::spawn(move || { - let (tx, rx) = ::std::sync::mpsc::channel(); - // The Duration argument is a debouncing period. - let mut watcher = - watcher(tx, Duration::from_millis(10)).expect("create file watcher"); - watcher - .watch(TEXT_SHADER_F_PATH, RecursiveMode::NonRecursive) - .expect("watch fragment shader"); - watcher - .watch(TEXT_SHADER_V_PATH, RecursiveMode::NonRecursive) - .expect("watch vertex shader"); - - loop { - let event = rx.recv().expect("watcher event"); - - match event { - DebouncedEvent::Rename(..) => continue, - DebouncedEvent::Create(_) - | DebouncedEvent::Write(_) - | DebouncedEvent::Chmod(_) => { - msg_tx.send(Msg::ShaderReload).expect("msg send ok"); - }, - _ => {}, - } - } - }); - } - - let mut renderer = QuadRenderer { - program, - rect_program, - vao, - ebo, - vbo_instance, - rect_vao, - rect_vbo, - atlas: Vec::new(), - current_atlas: 0, - active_tex: 0, - batch: Batch::new(), - rx: msg_rx, - }; - - let atlas = Atlas::new(ATLAS_SIZE); - renderer.atlas.push(atlas); - - Ok(renderer) - } - - // Draw all rectangles simultaneously to prevent excessive program swaps - pub fn draw_rects( - &mut self, - config: &Config, - props: &term::SizeInfo, - visual_bell_intensity: f64, - cell_line_rects: Rects, - ) { - // Swap to rectangle rendering program - unsafe { - // Swap program - gl::UseProgram(self.rect_program.id); - - // Remove padding from viewport - gl::Viewport(0, 0, props.width as i32, props.height as i32); - - // Change blending strategy - gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); - - // Setup data and buffers - gl::BindVertexArray(self.rect_vao); - gl::BindBuffer(gl::ARRAY_BUFFER, self.rect_vbo); - - // Position - gl::VertexAttribPointer( - 0, - 2, - gl::FLOAT, - gl::FALSE, - (size_of::<f32>() * 2) as _, - ptr::null(), - ); - gl::EnableVertexAttribArray(0); - } - - // Draw visual bell - let color = config.visual_bell().color(); - let rect = Rect::new(0., 0., props.width, props.height); - self.render_rect(&rect, color, visual_bell_intensity as f32, props); - - // Draw underlines and strikeouts - for cell_line_rect in cell_line_rects.rects() { - self.render_rect(&cell_line_rect.0, cell_line_rect.1, 255., props); - } - - // Deactivate rectangle program again - unsafe { - // Reset blending strategy - gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); - - // Reset data and buffers - gl::BindBuffer(gl::ARRAY_BUFFER, 0); - gl::BindVertexArray(0); - - let padding_x = props.padding_x as i32; - let padding_y = props.padding_y as i32; - let width = props.width as i32; - let height = props.height as i32; - gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y); - - // Disable program - gl::UseProgram(0); - } - } - - pub fn with_api<F, T>(&mut self, config: &Config, props: &term::SizeInfo, func: F) -> T - where - F: FnOnce(RenderApi<'_>) -> T, - { - // Flush message queue - if let Ok(Msg::ShaderReload) = self.rx.try_recv() { - self.reload_shaders(props); - } - while let Ok(_) = self.rx.try_recv() {} - - unsafe { - gl::UseProgram(self.program.id); - self.program.set_term_uniforms(props); - - gl::BindVertexArray(self.vao); - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo); - gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo_instance); - gl::ActiveTexture(gl::TEXTURE0); - } - - let res = func(RenderApi { - active_tex: &mut self.active_tex, - batch: &mut self.batch, - atlas: &mut self.atlas, - current_atlas: &mut self.current_atlas, - program: &mut self.program, - config, - }); - - unsafe { - gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); - gl::BindBuffer(gl::ARRAY_BUFFER, 0); - gl::BindVertexArray(0); - - gl::UseProgram(0); - } - - res - } - - pub fn with_loader<F, T>(&mut self, func: F) -> T - where - F: FnOnce(LoaderApi<'_>) -> T, - { - unsafe { - gl::ActiveTexture(gl::TEXTURE0); - } - - func(LoaderApi { - active_tex: &mut self.active_tex, - atlas: &mut self.atlas, - current_atlas: &mut self.current_atlas, - }) - } - - pub fn reload_shaders(&mut self, props: &term::SizeInfo) { - info!("Reloading shaders..."); - let result = (TextShaderProgram::new(), RectShaderProgram::new()); - let (program, rect_program) = match result { - (Ok(program), Ok(rect_program)) => { - unsafe { - gl::UseProgram(program.id); - program.update_projection( - props.width, - props.height, - props.padding_x, - props.padding_y, - ); - gl::UseProgram(0); - } - - info!("... successfully reloaded shaders"); - (program, rect_program) - }, - (Err(err), _) | (_, Err(err)) => { - error!("{}", err); - return; - }, - }; - - self.active_tex = 0; - self.program = program; - self.rect_program = rect_program; - } - - pub fn resize(&mut self, size: PhysicalSize, padding_x: f32, padding_y: f32) { - let (width, height): (u32, u32) = size.into(); - - // viewport - unsafe { - let width = width as i32; - let height = height as i32; - let padding_x = padding_x as i32; - let padding_y = padding_y as i32; - gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y); - - // update projection - gl::UseProgram(self.program.id); - self.program.update_projection( - width as f32, - height as f32, - padding_x as f32, - padding_y as f32, - ); - gl::UseProgram(0); - } - } - - // Render a rectangle - // - // This requires the rectangle program to be activated - fn render_rect(&mut self, rect: &Rect<f32>, color: Rgb, alpha: f32, size: &term::SizeInfo) { - // Do nothing when alpha is fully transparent - if alpha == 0. { - return; - } - - // Calculate rectangle position - let center_x = size.width / 2.; - let center_y = size.height / 2.; - let x = (rect.x - center_x) / center_x; - let y = -(rect.y - center_y) / center_y; - let width = rect.width / center_x; - let height = rect.height / center_y; - - unsafe { - // Setup vertices - let vertices: [f32; 8] = [x + width, y, x + width, y - height, x, y - height, x, y]; - - // Load vertex data into array buffer - gl::BufferData( - gl::ARRAY_BUFFER, - (size_of::<f32>() * vertices.len()) as _, - vertices.as_ptr() as *const _, - gl::STATIC_DRAW, - ); - - // Color - self.rect_program.set_color(color, alpha); - - // Draw the rectangle - gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null()); - } - } -} - -impl<'a> RenderApi<'a> { - pub fn clear(&self, color: Rgb) { - let alpha = self.config.background_opacity().get(); - unsafe { - gl::ClearColor( - (f32::from(color.r) / 255.0).min(1.0) * alpha, - (f32::from(color.g) / 255.0).min(1.0) * alpha, - (f32::from(color.b) / 255.0).min(1.0) * alpha, - alpha, - ); - gl::Clear(gl::COLOR_BUFFER_BIT); - } - } - - fn render_batch(&mut self) { - unsafe { - gl::BufferSubData( - gl::ARRAY_BUFFER, - 0, - self.batch.size() as isize, - self.batch.instances.as_ptr() as *const _, - ); - } - - // Bind texture if necessary - if *self.active_tex != self.batch.tex { - unsafe { - gl::BindTexture(gl::TEXTURE_2D, self.batch.tex); - } - *self.active_tex = self.batch.tex; - } - - unsafe { - self.program.set_background_pass(true); - gl::DrawElementsInstanced( - gl::TRIANGLES, - 6, - gl::UNSIGNED_INT, - ptr::null(), - self.batch.len() as GLsizei, - ); - self.program.set_background_pass(false); - gl::DrawElementsInstanced( - gl::TRIANGLES, - 6, - gl::UNSIGNED_INT, - ptr::null(), - self.batch.len() as GLsizei, - ); - } - - self.batch.clear(); - } - - /// Render a string in a variable location. Used for printing the render timer, warnings and - /// errors. - pub fn render_string( - &mut self, - string: &str, - line: Line, - glyph_cache: &mut GlyphCache, - color: Option<Rgb>, - ) { - let bg_alpha = color.map(|_| 1.0).unwrap_or(0.0); - let col = Column(0); - - let cells = string - .chars() - .enumerate() - .map(|(i, c)| RenderableCell { - line, - column: col + i, - inner: RenderableCellContent::Chars({ - let mut chars = [' '; cell::MAX_ZEROWIDTH_CHARS + 1]; - chars[0] = c; - chars - }), - bg: color.unwrap_or(Rgb { r: 0, g: 0, b: 0 }), - fg: Rgb { r: 0, g: 0, b: 0 }, - flags: cell::Flags::empty(), - bg_alpha, - }) - .collect::<Vec<_>>(); - - for cell in cells { - self.render_cell(cell, glyph_cache); - } - } - - #[inline] - fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph) { - // Flush batch if tex changing - if !self.batch.is_empty() && self.batch.tex != glyph.tex_id { - self.render_batch(); - } - - self.batch.add_item(cell, glyph); - - // Render batch and clear if it's full - if self.batch.full() { - self.render_batch(); - } - } - - pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache) { - let chars = match cell.inner { - RenderableCellContent::Cursor((cursor_style, ref raw)) => { - // Raw cell pixel buffers like cursors don't need to go through font lookup - let glyph = glyph_cache - .cursor_cache - .entry(cursor_style) - .or_insert_with(|| self.load_glyph(raw)); - self.add_render_item(&cell, &glyph); - return; - }, - RenderableCellContent::Chars(chars) => chars, - }; - - // Get font key for cell - // FIXME this is super inefficient. - let font_key = if cell.flags.contains(cell::Flags::BOLD) { - glyph_cache.bold_key - } else if cell.flags.contains(cell::Flags::ITALIC) { - glyph_cache.italic_key - } else { - glyph_cache.font_key - }; - - // Don't render text of HIDDEN cells - let mut chars = if cell.flags.contains(cell::Flags::HIDDEN) { - [' '; cell::MAX_ZEROWIDTH_CHARS + 1] - } else { - chars - }; - - // Render tabs as spaces in case the font doesn't support it - if chars[0] == '\t' { - chars[0] = ' '; - } - - let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, c: chars[0] }; - - // Add cell to batch - let glyph = glyph_cache.get(glyph_key, self); - self.add_render_item(&cell, glyph); - - // Render zero-width characters - for c in (&chars[1..]).iter().filter(|c| **c != ' ') { - glyph_key.c = *c; - let mut glyph = *glyph_cache.get(glyph_key, self); - - // The metrics of zero-width characters are based on rendering - // the character after the current cell, with the anchor at the - // right side of the preceding character. Since we render the - // zero-width characters inside the preceding character, the - // anchor has been moved to the right by one cell. - glyph.left += glyph_cache.metrics.average_advance as f32; - - self.add_render_item(&cell, &glyph); - } - } -} - -/// Load a glyph into a texture atlas -/// -/// If the current atlas is full, a new one will be created. -#[inline] -fn load_glyph( - active_tex: &mut GLuint, - atlas: &mut Vec<Atlas>, - current_atlas: &mut usize, - rasterized: &RasterizedGlyph, -) -> Glyph { - // At least one atlas is guaranteed to be in the `self.atlas` list; thus - // the unwrap. - match atlas[*current_atlas].insert(rasterized, active_tex) { - Ok(glyph) => glyph, - Err(AtlasInsertError::Full) => { - *current_atlas += 1; - if *current_atlas == atlas.len() { - let new = Atlas::new(ATLAS_SIZE); - *active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy. - atlas.push(new); - } - load_glyph(active_tex, atlas, current_atlas, rasterized) - }, - Err(AtlasInsertError::GlyphTooLarge) => Glyph { - tex_id: atlas[*current_atlas].id, - top: 0.0, - left: 0.0, - width: 0.0, - height: 0.0, - uv_bot: 0.0, - uv_left: 0.0, - uv_width: 0.0, - uv_height: 0.0, - }, - } -} - -#[inline] -fn clear_atlas(atlas: &mut Vec<Atlas>, current_atlas: &mut usize) { - for atlas in atlas.iter_mut() { - atlas.clear(); - } - *current_atlas = 0; -} - -impl<'a> LoadGlyph for LoaderApi<'a> { - fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { - load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) - } - - fn clear(&mut self) { - clear_atlas(self.atlas, self.current_atlas) - } -} - -impl<'a> LoadGlyph for RenderApi<'a> { - fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { - load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized) - } - - fn clear(&mut self) { - clear_atlas(self.atlas, self.current_atlas) - } -} - -impl<'a> Drop for RenderApi<'a> { - fn drop(&mut self) { - if !self.batch.is_empty() { - self.render_batch(); - } - } -} - -impl TextShaderProgram { - pub fn new() -> Result<TextShaderProgram, ShaderCreationError> { - let (vertex_src, fragment_src) = if cfg!(feature = "live-shader-reload") { - (None, None) - } else { - (Some(TEXT_SHADER_V), Some(TEXT_SHADER_F)) - }; - let vertex_shader = create_shader(TEXT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_src)?; - let fragment_shader = create_shader(TEXT_SHADER_F_PATH, gl::FRAGMENT_SHADER, fragment_src)?; - let program = create_program(vertex_shader, fragment_shader)?; - - unsafe { - gl::DeleteShader(fragment_shader); - gl::DeleteShader(vertex_shader); - gl::UseProgram(program); - } - - macro_rules! cptr { - ($thing:expr) => { - $thing.as_ptr() as *const _ - }; - } - - macro_rules! assert_uniform_valid { - ($uniform:expr) => { - assert!($uniform != gl::INVALID_VALUE as i32); - assert!($uniform != gl::INVALID_OPERATION as i32); - }; - ( $( $uniform:expr ),* ) => { - $( assert_uniform_valid!($uniform); )* - }; - } - - // get uniform locations - let (projection, cell_dim, background) = unsafe { - ( - gl::GetUniformLocation(program, cptr!(b"projection\0")), - gl::GetUniformLocation(program, cptr!(b"cellDim\0")), - gl::GetUniformLocation(program, cptr!(b"backgroundPass\0")), - ) - }; - - assert_uniform_valid!(projection, cell_dim, background); - - let shader = TextShaderProgram { - id: program, - u_projection: projection, - u_cell_dim: cell_dim, - u_background: background, - }; - - unsafe { - gl::UseProgram(0); - } - - Ok(shader) - } - - fn update_projection(&self, width: f32, height: f32, padding_x: f32, padding_y: f32) { - // Bounds check - if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) { - return; - } - - // Compute scale and offset factors, from pixel to ndc space. Y is inverted - // [0, width - 2 * padding_x] to [-1, 1] - // [height - 2 * padding_y, 0] to [-1, 1] - let scale_x = 2. / (width - 2. * padding_x); - let scale_y = -2. / (height - 2. * padding_y); - let offset_x = -1.; - let offset_y = 1.; - - info!("Width: {}, Height: {}", width, height); - - unsafe { - gl::Uniform4f(self.u_projection, offset_x, offset_y, scale_x, scale_y); - } - } - - fn set_term_uniforms(&self, props: &term::SizeInfo) { - unsafe { - gl::Uniform2f(self.u_cell_dim, props.cell_width, props.cell_height); - } - } - - fn set_background_pass(&self, background_pass: bool) { - let value = if background_pass { 1 } else { 0 }; - - unsafe { - gl::Uniform1i(self.u_background, value); - } - } -} - -impl Drop for TextShaderProgram { - fn drop(&mut self) { - unsafe { - gl::DeleteProgram(self.id); - } - } -} - -impl RectShaderProgram { - pub fn new() -> Result<Self, ShaderCreationError> { - let (vertex_src, fragment_src) = if cfg!(feature = "live-shader-reload") { - (None, None) - } else { - (Some(RECT_SHADER_V), Some(RECT_SHADER_F)) - }; - let vertex_shader = create_shader(RECT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_src)?; - let fragment_shader = create_shader(RECT_SHADER_F_PATH, gl::FRAGMENT_SHADER, fragment_src)?; - let program = create_program(vertex_shader, fragment_shader)?; - - unsafe { - gl::DeleteShader(fragment_shader); - gl::DeleteShader(vertex_shader); - gl::UseProgram(program); - } - - // get uniform locations - let u_color = unsafe { gl::GetUniformLocation(program, b"color\0".as_ptr() as *const _) }; - - let shader = RectShaderProgram { id: program, u_color }; - - unsafe { gl::UseProgram(0) } - - Ok(shader) - } - - fn set_color(&self, color: Rgb, alpha: f32) { - unsafe { - gl::Uniform4f( - self.u_color, - f32::from(color.r) / 255., - f32::from(color.g) / 255., - f32::from(color.b) / 255., - alpha, - ); - } - } -} - -impl Drop for RectShaderProgram { - fn drop(&mut self) { - unsafe { - gl::DeleteProgram(self.id); - } - } -} - -fn create_program(vertex: GLuint, fragment: GLuint) -> Result<GLuint, ShaderCreationError> { - unsafe { - let program = gl::CreateProgram(); - gl::AttachShader(program, vertex); - gl::AttachShader(program, fragment); - gl::LinkProgram(program); - - let mut success: GLint = 0; - gl::GetProgramiv(program, gl::LINK_STATUS, &mut success); - - if success == i32::from(gl::TRUE) { - Ok(program) - } else { - Err(ShaderCreationError::Link(get_program_info_log(program))) - } - } -} - -fn create_shader( - path: &str, - kind: GLenum, - source: Option<&'static str>, -) -> Result<GLuint, ShaderCreationError> { - let from_disk; - let source = if let Some(src) = source { - src - } else { - from_disk = read_file(path)?; - &from_disk[..] - }; - - let len: [GLint; 1] = [source.len() as GLint]; - - let shader = unsafe { - let shader = gl::CreateShader(kind); - gl::ShaderSource(shader, 1, &(source.as_ptr() as *const _), len.as_ptr()); - gl::CompileShader(shader); - shader - }; - - let mut success: GLint = 0; - unsafe { - gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success); - } - - if success == GLint::from(gl::TRUE) { - Ok(shader) - } else { - // Read log - let log = get_shader_info_log(shader); - - // Cleanup - unsafe { - gl::DeleteShader(shader); - } - - Err(ShaderCreationError::Compile(PathBuf::from(path), log)) - } -} - -fn get_program_info_log(program: GLuint) -> String { - // Get expected log length - let mut max_length: GLint = 0; - unsafe { - gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut max_length); - } - - // Read the info log - let mut actual_length: GLint = 0; - let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize); - unsafe { - gl::GetProgramInfoLog(program, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); - } - - // Build a string - unsafe { - buf.set_len(actual_length as usize); - } - - // XXX should we expect opengl to return garbage? - String::from_utf8(buf).unwrap() -} - -fn get_shader_info_log(shader: GLuint) -> String { - // Get expected log length - let mut max_length: GLint = 0; - unsafe { - gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut max_length); - } - - // Read the info log - let mut actual_length: GLint = 0; - let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize); - unsafe { - gl::GetShaderInfoLog(shader, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); - } - - // Build a string - unsafe { - buf.set_len(actual_length as usize); - } - - // XXX should we expect opengl to return garbage? - String::from_utf8(buf).unwrap() -} - -fn read_file(path: &str) -> Result<String, io::Error> { - let mut f = File::open(path)?; - let mut buf = String::new(); - f.read_to_string(&mut buf)?; - - Ok(buf) -} - -#[derive(Debug)] -pub enum ShaderCreationError { - /// Error reading file - Io(io::Error), - - /// Error compiling shader - Compile(PathBuf, String), - - /// Problem linking - Link(String), -} - -impl ::std::error::Error for ShaderCreationError { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - ShaderCreationError::Io(ref err) => Some(err), - _ => None, - } - } - - fn description(&self) -> &str { - match *self { - ShaderCreationError::Io(ref err) => err.description(), - ShaderCreationError::Compile(ref _path, ref s) => s.as_str(), - ShaderCreationError::Link(ref s) => s.as_str(), - } - } -} - -impl ::std::fmt::Display for ShaderCreationError { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - ShaderCreationError::Io(ref err) => write!(f, "Couldn't read shader: {}", err), - ShaderCreationError::Compile(ref path, ref log) => { - write!(f, "Failed compiling shader at {}: {}", path.display(), log) - }, - ShaderCreationError::Link(ref log) => write!(f, "Failed linking shader: {}", log), - } - } -} - -impl From<io::Error> for ShaderCreationError { - fn from(val: io::Error) -> ShaderCreationError { - ShaderCreationError::Io(val) - } -} - -/// Manages a single texture atlas -/// -/// The strategy for filling an atlas looks roughly like this: -/// -/// ```ignore -/// (width, height) -/// ┌─────┬─────┬─────┬─────┬─────┐ -/// │ 10 │ │ │ │ │ <- Empty spaces; can be filled while -/// │ │ │ │ │ │ glyph_height < height - row_baseline -/// ├⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┤ -/// │ 5 │ 6 │ 7 │ 8 │ 9 │ -/// │ │ │ │ │ │ -/// ├⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┼⎼⎼⎼⎼⎼┴⎼⎼⎼⎼⎼┤ <- Row height is tallest glyph in row; this is -/// │ 1 │ 2 │ 3 │ 4 │ used as the baseline for the following row. -/// │ │ │ │ │ <- Row considered full when next glyph doesn't -/// └─────┴─────┴─────┴───────────┘ fit in the row. -/// (0, 0) x-> -/// ``` -#[derive(Debug)] -struct Atlas { - /// Texture id for this atlas - id: GLuint, - - /// Width of atlas - width: i32, - - /// Height of atlas - height: i32, - - /// Left-most free pixel in a row. - /// - /// This is called the extent because it is the upper bound of used pixels - /// in a row. - row_extent: i32, - - /// Baseline for glyphs in the current row - row_baseline: i32, - - /// Tallest glyph in current row - /// - /// This is used as the advance when end of row is reached - row_tallest: i32, -} - -/// Error that can happen when inserting a texture to the Atlas -enum AtlasInsertError { - /// Texture atlas is full - Full, - - /// The glyph cannot fit within a single texture - GlyphTooLarge, -} - -impl Atlas { - fn new(size: i32) -> Atlas { - let mut id: GLuint = 0; - unsafe { - gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1); - gl::GenTextures(1, &mut id); - gl::BindTexture(gl::TEXTURE_2D, id); - gl::TexImage2D( - gl::TEXTURE_2D, - 0, - gl::RGB as i32, - size, - size, - 0, - gl::RGB, - gl::UNSIGNED_BYTE, - ptr::null(), - ); - - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); - - gl::BindTexture(gl::TEXTURE_2D, 0); - } - - Atlas { id, width: size, height: size, row_extent: 0, row_baseline: 0, row_tallest: 0 } - } - - pub fn clear(&mut self) { - self.row_extent = 0; - self.row_baseline = 0; - self.row_tallest = 0; - } - - /// Insert a RasterizedGlyph into the texture atlas - pub fn insert( - &mut self, - glyph: &RasterizedGlyph, - active_tex: &mut u32, - ) -> Result<Glyph, AtlasInsertError> { - if glyph.width > self.width || glyph.height > self.height { - return Err(AtlasInsertError::GlyphTooLarge); - } - - // If there's not enough room in current row, go onto next one - if !self.room_in_row(glyph) { - self.advance_row()?; - } - - // If there's still not room, there's nothing that can be done here. - if !self.room_in_row(glyph) { - return Err(AtlasInsertError::Full); - } - - // There appears to be room; load the glyph. - Ok(self.insert_inner(glyph, active_tex)) - } - - /// Insert the glyph without checking for room - /// - /// Internal function for use once atlas has been checked for space. GL - /// errors could still occur at this point if we were checking for them; - /// hence, the Result. - fn insert_inner(&mut self, glyph: &RasterizedGlyph, active_tex: &mut u32) -> Glyph { - let offset_y = self.row_baseline; - let offset_x = self.row_extent; - let height = glyph.height as i32; - let width = glyph.width as i32; - - unsafe { - gl::BindTexture(gl::TEXTURE_2D, self.id); - - // Load data into OpenGL - gl::TexSubImage2D( - gl::TEXTURE_2D, - 0, - offset_x, - offset_y, - width, - height, - gl::RGB, - gl::UNSIGNED_BYTE, - glyph.buf.as_ptr() as *const _, - ); - - gl::BindTexture(gl::TEXTURE_2D, 0); - *active_tex = 0; - } - - // Update Atlas state - self.row_extent = offset_x + width; - if height > self.row_tallest { - self.row_tallest = height; - } - - // Generate UV coordinates - let uv_bot = offset_y as f32 / self.height as f32; - let uv_left = offset_x as f32 / self.width as f32; - let uv_height = height as f32 / self.height as f32; - let uv_width = width as f32 / self.width as f32; - - Glyph { - tex_id: self.id, - top: glyph.top as f32, - width: width as f32, - height: height as f32, - left: glyph.left as f32, - uv_bot, - uv_left, - uv_width, - uv_height, - } - } - - /// Check if there's room in the current row for given glyph - fn room_in_row(&self, raw: &RasterizedGlyph) -> bool { - let next_extent = self.row_extent + raw.width as i32; - let enough_width = next_extent <= self.width; - let enough_height = (raw.height as i32) < (self.height - self.row_baseline); - - enough_width && enough_height - } - - /// Mark current row as finished and prepare to insert into the next row - fn advance_row(&mut self) -> Result<(), AtlasInsertError> { - let advance_to = self.row_baseline + self.row_tallest; - if self.height - advance_to <= 0 { - return Err(AtlasInsertError::Full); - } - - self.row_baseline = advance_to; - self.row_extent = 0; - self.row_tallest = 0; - - Ok(()) - } -} diff --git a/src/renderer/rects.rs b/src/renderer/rects.rs deleted file mode 100644 index b4f8a012..00000000 --- a/src/renderer/rects.rs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use font::Metrics; - -use crate::index::Point; -use crate::term::cell::Flags; -use crate::term::color::Rgb; -use crate::term::{RenderableCell, SizeInfo}; - -#[derive(Debug, Copy, Clone)] -pub struct Rect<T> { - pub x: T, - pub y: T, - pub width: T, - pub height: T, -} - -impl<T> Rect<T> { - pub fn new(x: T, y: T, width: T, height: T) -> Self { - Rect { x, y, width, height } - } -} - -#[derive(Debug)] -struct Line { - flag: Flags, - range: Option<(RenderableCell, Point)>, -} - -impl Line { - fn new(flag: Flags) -> Self { - Self { flag, range: None } - } -} - -/// Rects for underline, strikeout and more. -pub struct Rects<'a> { - inner: Vec<(Rect<f32>, Rgb)>, - active_lines: Vec<Line>, - metrics: &'a Metrics, - size: &'a SizeInfo, -} - -impl<'a> Rects<'a> { - pub fn new(metrics: &'a Metrics, size: &'a SizeInfo) -> Self { - let active_lines = vec![Line::new(Flags::UNDERLINE), Line::new(Flags::STRIKEOUT)]; - Self { inner: Vec::new(), active_lines, metrics, size } - } - - /// Convert the stored rects to rectangles for the renderer. - pub fn rects(&self) -> &Vec<(Rect<f32>, Rgb)> { - &self.inner - } - - /// Update the stored lines with the next cell info. - pub fn update_lines(&mut self, size_info: &SizeInfo, cell: &RenderableCell) { - for line in self.active_lines.iter_mut() { - match line.range { - // Check for end if line is present - Some((ref mut start, ref mut end)) => { - // No change in line - if cell.line == start.line - && cell.flags.contains(line.flag) - && cell.fg == start.fg - && cell.column == end.col + 1 - { - if size_info.cols() == cell.column && size_info.lines() == cell.line { - // Add the last rect if we've reached the end of the terminal - self.inner.push(create_rect( - &start, - cell.into(), - line.flag, - &self.metrics, - &self.size, - )); - } else { - // Update the length of the line - *end = cell.into(); - } - - continue; - } - - self.inner.push(create_rect(start, *end, line.flag, &self.metrics, &self.size)); - - // Start a new line if the flag is present - if cell.flags.contains(line.flag) { - *start = cell.clone(); - *end = cell.into(); - } else { - line.range = None; - } - }, - // Check for new start of line - None => { - if cell.flags.contains(line.flag) { - line.range = Some((cell.clone(), cell.into())); - } - }, - }; - } - } - - // Add a rectangle - pub fn push(&mut self, rect: Rect<f32>, color: Rgb) { - self.inner.push((rect, color)); - } -} - -/// Create a rectangle that starts on the left of `start` and ends on the right -/// of `end`, based on the given flag and size metrics. -fn create_rect( - start: &RenderableCell, - end: Point, - flag: Flags, - metrics: &Metrics, - size: &SizeInfo, -) -> (Rect<f32>, Rgb) { - let start_x = start.column.0 as f32 * size.cell_width; - let end_x = (end.col.0 + 1) as f32 * size.cell_width; - let width = end_x - start_x; - - let (position, mut height) = match flag { - Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness), - Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness), - _ => unimplemented!("Invalid flag for cell line drawing specified"), - }; - - // Make sure lines are always visible - height = height.max(1.); - - let cell_bottom = (start.line.0 as f32 + 1.) * size.cell_height; - let baseline = cell_bottom + metrics.descent; - - let mut y = baseline - position - height / 2.; - let max_y = cell_bottom - height; - if y > max_y { - y = max_y; - } - - let rect = - Rect::new(start_x + size.padding_x, y.round() + size.padding_y, width, height.round()); - - (rect, start.fg) -} diff --git a/src/selection.rs b/src/selection.rs deleted file mode 100644 index d2009586..00000000 --- a/src/selection.rs +++ /dev/null @@ -1,571 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! State management for a selection in the grid -//! -//! A selection should start when the mouse is clicked, and it should be -//! finalized when the button is released. The selection should be cleared -//! when text is added/removed/scrolled on the screen. The selection should -//! also be cleared if the user clicks off of the selection. -use std::cmp::{max, min}; -use std::ops::Range; - -use crate::index::{Column, Point, Side}; -use crate::term::Search; - -/// Describes a region of a 2-dimensional area -/// -/// Used to track a text selection. There are three supported modes, each with its own constructor: -/// [`simple`], [`semantic`], and [`lines`]. The [`simple`] mode precisely tracks which cells are -/// selected without any expansion. [`semantic`] mode expands the initial selection to the nearest -/// semantic escape char in either direction. [`lines`] will always select entire lines. -/// -/// Calls to [`update`] operate different based on the selection kind. The [`simple`] mode does -/// nothing special, simply tracks points and sides. [`semantic`] will continue to expand out to -/// semantic boundaries as the selection point changes. Similarly, [`lines`] will always expand the -/// new point to encompass entire lines. -/// -/// [`simple`]: enum.Selection.html#method.simple -/// [`semantic`]: enum.Selection.html#method.semantic -/// [`lines`]: enum.Selection.html#method.lines -#[derive(Debug, Clone, PartialEq)] -pub enum Selection { - Simple { - /// The region representing start and end of cursor movement - region: Range<Anchor>, - }, - Semantic { - /// The region representing start and end of cursor movement - region: Range<Point<isize>>, - }, - Lines { - /// The region representing start and end of cursor movement - region: Range<Point<isize>>, - - /// The line under the initial point. This is always selected regardless - /// of which way the cursor is moved. - initial_line: isize, - }, -} - -/// A Point and side within that point. -#[derive(Debug, Clone, PartialEq)] -pub struct Anchor { - point: Point<isize>, - side: Side, -} - -impl Anchor { - fn new(point: Point<isize>, side: Side) -> Anchor { - Anchor { point, side } - } -} - -/// A type that has 2-dimensional boundaries -pub trait Dimensions { - /// Get the size of the area - fn dimensions(&self) -> Point; -} - -impl Selection { - pub fn simple(location: Point<usize>, side: Side) -> Selection { - Selection::Simple { - region: Range { - start: Anchor::new(location.into(), side), - end: Anchor::new(location.into(), side), - }, - } - } - - pub fn rotate(&mut self, offset: isize) { - match *self { - Selection::Simple { ref mut region } => { - region.start.point.line += offset; - region.end.point.line += offset; - }, - Selection::Semantic { ref mut region } => { - region.start.line += offset; - region.end.line += offset; - }, - Selection::Lines { ref mut region, ref mut initial_line } => { - region.start.line += offset; - region.end.line += offset; - *initial_line += offset; - }, - } - } - - pub fn semantic(point: Point<usize>) -> Selection { - Selection::Semantic { region: Range { start: point.into(), end: point.into() } } - } - - pub fn lines(point: Point<usize>) -> Selection { - Selection::Lines { - region: Range { start: point.into(), end: point.into() }, - initial_line: point.line as isize, - } - } - - pub fn update(&mut self, location: Point<usize>, side: Side) { - // Always update the `end`; can normalize later during span generation. - match *self { - Selection::Simple { ref mut region } => { - region.end = Anchor::new(location.into(), side); - }, - Selection::Semantic { ref mut region } | Selection::Lines { ref mut region, .. } => { - region.end = location.into(); - }, - } - } - - pub fn to_span<G>(&self, grid: &G, alt_screen: bool) -> Option<Span> - where - G: Search + Dimensions, - { - match *self { - Selection::Simple { ref region } => Selection::span_simple(grid, region, alt_screen), - Selection::Semantic { ref region } => { - Selection::span_semantic(grid, region, alt_screen) - }, - Selection::Lines { ref region, initial_line } => { - Selection::span_lines(grid, region, initial_line, alt_screen) - }, - } - } - - pub fn is_empty(&self) -> bool { - match *self { - Selection::Simple { ref region } => { - region.start == region.end && region.start.side == region.end.side - }, - Selection::Semantic { .. } | Selection::Lines { .. } => false, - } - } - - fn span_semantic<G>(grid: &G, region: &Range<Point<isize>>, alt_screen: bool) -> Option<Span> - where - G: Search + Dimensions, - { - let cols = grid.dimensions().col; - let lines = grid.dimensions().line.0 as isize; - - // Normalize ordering of selected cells - let (mut front, mut tail) = if region.start < region.end { - (region.start, region.end) - } else { - (region.end, region.start) - }; - - if alt_screen { - Selection::alt_screen_clamp(&mut front, &mut tail, lines, cols)?; - } - - let (mut start, mut end) = if front < tail && front.line == tail.line { - (grid.semantic_search_left(front.into()), grid.semantic_search_right(tail.into())) - } else { - (grid.semantic_search_right(front.into()), grid.semantic_search_left(tail.into())) - }; - - if start > end { - ::std::mem::swap(&mut start, &mut end); - } - - Some(Span { cols, front: start, tail: end, ty: SpanType::Inclusive }) - } - - fn span_lines<G>( - grid: &G, - region: &Range<Point<isize>>, - initial_line: isize, - alt_screen: bool, - ) -> Option<Span> - where - G: Dimensions, - { - let cols = grid.dimensions().col; - let lines = grid.dimensions().line.0 as isize; - - // First, create start and end points based on initial line and the grid - // dimensions. - let mut start = Point { col: cols - 1, line: initial_line }; - let mut end = Point { col: Column(0), line: initial_line }; - - // Now, expand lines based on where cursor started and ended. - if region.start.line < region.end.line { - // Start is below end - start.line = min(start.line, region.start.line); - end.line = max(end.line, region.end.line); - } else { - // Start is above end - start.line = min(start.line, region.end.line); - end.line = max(end.line, region.start.line); - } - - if alt_screen { - Selection::alt_screen_clamp(&mut start, &mut end, lines, cols)?; - } - - Some(Span { cols, front: start.into(), tail: end.into(), ty: SpanType::Inclusive }) - } - - fn span_simple<G>(grid: &G, region: &Range<Anchor>, alt_screen: bool) -> Option<Span> - where - G: Dimensions, - { - let start = region.start.point; - let start_side = region.start.side; - let end = region.end.point; - let end_side = region.end.side; - let cols = grid.dimensions().col; - let lines = grid.dimensions().line.0 as isize; - - // Make sure front is always the "bottom" and tail is always the "top" - let (mut front, mut tail, front_side, tail_side) = - if start.line > end.line || start.line == end.line && start.col <= end.col { - // Selected upward; start/end are swapped - (end, start, end_side, start_side) - } else { - // Selected downward; no swapping - (start, end, start_side, end_side) - }; - - // No selection for single cell with identical sides or two cell with right+left sides - if (front == tail && front_side == tail_side) - || (tail_side == Side::Right - && front_side == Side::Left - && front.line == tail.line - && front.col == tail.col + 1) - { - return None; - } - - // Remove last cell if selection ends to the left of a cell - if front_side == Side::Left && start != end { - // Special case when selection starts to left of first cell - if front.col == Column(0) { - front.col = cols - 1; - front.line += 1; - } else { - front.col -= 1; - } - } - - // Remove first cell if selection starts at the right of a cell - if tail_side == Side::Right && front != tail { - tail.col += 1; - } - - if alt_screen { - Selection::alt_screen_clamp(&mut front, &mut tail, lines, cols)?; - } - - // Return the selection with all cells inclusive - Some(Span { cols, front: front.into(), tail: tail.into(), ty: SpanType::Inclusive }) - } - - // Clamp selection in the alternate screen to the visible region - fn alt_screen_clamp( - front: &mut Point<isize>, - tail: &mut Point<isize>, - lines: isize, - cols: Column, - ) -> Option<()> { - if tail.line >= lines { - // Don't show selection above visible region - if front.line >= lines { - return None; - } - - // Clamp selection above viewport to visible region - tail.line = lines - 1; - tail.col = Column(0); - } - - if front.line < 0 { - // Don't show selection below visible region - if tail.line < 0 { - return None; - } - - // Clamp selection below viewport to visible region - front.line = 0; - front.col = cols - 1; - } - - Some(()) - } -} - -/// How to interpret the locations of a Span. -#[derive(Debug, Eq, PartialEq)] -pub enum SpanType { - /// Includes the beginning and end locations - Inclusive, - - /// Exclude both beginning and end - Exclusive, - - /// Excludes last cell of selection - ExcludeTail, - - /// Excludes first cell of selection - ExcludeFront, -} - -/// Represents a span of selected cells -#[derive(Debug, Eq, PartialEq)] -pub struct Span { - front: Point<usize>, - tail: Point<usize>, - cols: Column, - - /// The type says whether ends are included or not. - ty: SpanType, -} - -#[derive(Debug)] -pub struct Locations { - /// Start point from bottom of buffer - pub start: Point<usize>, - /// End point towards top of buffer - pub end: Point<usize>, -} - -impl Span { - pub fn to_locations(&self) -> Locations { - let (start, end) = match self.ty { - SpanType::Inclusive => (self.front, self.tail), - SpanType::Exclusive => { - (Span::wrap_start(self.front, self.cols), Span::wrap_end(self.tail, self.cols)) - }, - SpanType::ExcludeFront => (Span::wrap_start(self.front, self.cols), self.tail), - SpanType::ExcludeTail => (self.front, Span::wrap_end(self.tail, self.cols)), - }; - - Locations { start, end } - } - - fn wrap_start(mut start: Point<usize>, cols: Column) -> Point<usize> { - if start.col == cols - 1 { - Point { line: start.line + 1, col: Column(0) } - } else { - start.col += 1; - start - } - } - - fn wrap_end(end: Point<usize>, cols: Column) -> Point<usize> { - if end.col == Column(0) && end.line != 0 { - Point { line: end.line - 1, col: cols } - } else { - Point { line: end.line, col: end.col - 1 } - } - } -} - -/// Tests for selection -/// -/// There are comments on all of the tests describing the selection. Pictograms -/// are used to avoid ambiguity. Grid cells are represented by a [ ]. Only -/// cells that are completely covered are counted in a selection. Ends are -/// represented by `B` and `E` for begin and end, respectively. A selected cell -/// looks like [XX], [BX] (at the start), [XB] (at the end), [XE] (at the end), -/// and [EX] (at the start), or [BE] for a single cell. Partially selected cells -/// look like [ B] and [E ]. -#[cfg(test)] -mod test { - use super::{Selection, Span, SpanType}; - use crate::index::{Column, Line, Point, Side}; - use crate::url::Url; - - struct Dimensions(Point); - impl super::Dimensions for Dimensions { - fn dimensions(&self) -> Point { - self.0 - } - } - - impl Dimensions { - pub fn new(line: usize, col: usize) -> Self { - Dimensions(Point { line: Line(line), col: Column(col) }) - } - } - - impl super::Search for Dimensions { - fn semantic_search_left(&self, point: Point<usize>) -> Point<usize> { - point - } - - fn semantic_search_right(&self, point: Point<usize>) -> Point<usize> { - point - } - - fn url_search(&self, _: Point<usize>) -> Option<Url> { - None - } - } - - /// Test case of single cell selection - /// - /// 1. [ ] - /// 2. [B ] - /// 3. [BE] - #[test] - fn single_cell_left_to_right() { - let location = Point { line: 0, col: Column(0) }; - let mut selection = Selection::simple(location, Side::Left); - selection.update(location, Side::Right); - - assert_eq!(selection.to_span(&Dimensions::new(1, 1), false).unwrap(), Span { - cols: Column(1), - ty: SpanType::Inclusive, - front: location, - tail: location - }); - } - - /// Test case of single cell selection - /// - /// 1. [ ] - /// 2. [ B] - /// 3. [EB] - #[test] - fn single_cell_right_to_left() { - let location = Point { line: 0, col: Column(0) }; - let mut selection = Selection::simple(location, Side::Right); - selection.update(location, Side::Left); - - assert_eq!(selection.to_span(&Dimensions::new(1, 1), false).unwrap(), Span { - cols: Column(1), - ty: SpanType::Inclusive, - front: location, - tail: location - }); - } - - /// Test adjacent cell selection from left to right - /// - /// 1. [ ][ ] - /// 2. [ B][ ] - /// 3. [ B][E ] - #[test] - fn between_adjacent_cells_left_to_right() { - let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right); - selection.update(Point::new(0, Column(1)), Side::Left); - - assert_eq!(selection.to_span(&Dimensions::new(1, 2), false), None); - } - - /// Test adjacent cell selection from right to left - /// - /// 1. [ ][ ] - /// 2. [ ][B ] - /// 3. [ E][B ] - #[test] - fn between_adjacent_cells_right_to_left() { - let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Left); - selection.update(Point::new(0, Column(0)), Side::Right); - - assert_eq!(selection.to_span(&Dimensions::new(1, 2), false), None); - } - - /// Test selection across adjacent lines - /// - /// - /// 1. [ ][ ][ ][ ][ ] - /// [ ][ ][ ][ ][ ] - /// 2. [ ][ B][ ][ ][ ] - /// [ ][ ][ ][ ][ ] - /// 3. [ ][ B][XX][XX][XX] - /// [XX][XE][ ][ ][ ] - #[test] - fn across_adjacent_lines_upward_final_cell_exclusive() { - let mut selection = Selection::simple(Point::new(1, Column(1)), Side::Right); - selection.update(Point::new(0, Column(1)), Side::Right); - - assert_eq!(selection.to_span(&Dimensions::new(2, 5), false).unwrap(), Span { - cols: Column(5), - front: Point::new(0, Column(1)), - tail: Point::new(1, Column(2)), - ty: SpanType::Inclusive, - }); - } - - /// Test selection across adjacent lines - /// - /// - /// 1. [ ][ ][ ][ ][ ] - /// [ ][ ][ ][ ][ ] - /// 2. [ ][ ][ ][ ][ ] - /// [ ][ B][ ][ ][ ] - /// 3. [ ][ E][XX][XX][XX] - /// [XX][XB][ ][ ][ ] - /// 4. [ E][XX][XX][XX][XX] - /// [XX][XB][ ][ ][ ] - #[test] - fn selection_bigger_then_smaller() { - let mut selection = Selection::simple(Point::new(0, Column(1)), Side::Right); - selection.update(Point::new(1, Column(1)), Side::Right); - selection.update(Point::new(1, Column(0)), Side::Right); - - assert_eq!(selection.to_span(&Dimensions::new(2, 5), false).unwrap(), Span { - cols: Column(5), - front: Point::new(0, Column(1)), - tail: Point::new(1, Column(1)), - ty: SpanType::Inclusive, - }); - } - - #[test] - fn alt_scren_lines() { - let mut selection = Selection::lines(Point::new(0, Column(0))); - selection.update(Point::new(5, Column(3)), Side::Right); - selection.rotate(-3); - - assert_eq!(selection.to_span(&Dimensions::new(10, 5), true).unwrap(), Span { - cols: Column(5), - front: Point::new(0, Column(4)), - tail: Point::new(2, Column(0)), - ty: SpanType::Inclusive, - }); - } - - #[test] - fn alt_screen_semantic() { - let mut selection = Selection::semantic(Point::new(0, Column(0))); - selection.update(Point::new(5, Column(3)), Side::Right); - selection.rotate(-3); - - assert_eq!(selection.to_span(&Dimensions::new(10, 5), true).unwrap(), Span { - cols: Column(5), - front: Point::new(0, Column(4)), - tail: Point::new(2, Column(3)), - ty: SpanType::Inclusive, - }); - } - - #[test] - fn alt_screen_simple() { - let mut selection = Selection::simple(Point::new(0, Column(0)), Side::Right); - selection.update(Point::new(5, Column(3)), Side::Right); - selection.rotate(-3); - - assert_eq!(selection.to_span(&Dimensions::new(10, 5), true).unwrap(), Span { - cols: Column(5), - front: Point::new(0, Column(4)), - tail: Point::new(2, Column(4)), - ty: SpanType::Inclusive, - }); - } -} diff --git a/src/sync.rs b/src/sync.rs deleted file mode 100644 index 0fcd0862..00000000 --- a/src/sync.rs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Synchronization types -//! -//! Most importantly, a fair mutex is included -use parking_lot::{Mutex, MutexGuard}; - -/// A fair mutex -/// -/// Uses an extra lock to ensure that if one thread is waiting that it will get -/// the lock before a single thread can re-lock it. -pub struct FairMutex<T> { - /// Data - data: Mutex<T>, - /// Next-to-access - next: Mutex<()>, -} - -impl<T> FairMutex<T> { - /// Create a new fair mutex - pub fn new(data: T) -> FairMutex<T> { - FairMutex { data: Mutex::new(data), next: Mutex::new(()) } - } - - /// Lock the mutex - pub fn lock(&self) -> MutexGuard<'_, T> { - // Must bind to a temporary or the lock will be freed before going - // into data.lock() - let _next = self.next.lock(); - self.data.lock() - } -} diff --git a/src/term/cell.rs b/src/term/cell.rs deleted file mode 100644 index 4d2f4c1c..00000000 --- a/src/term/cell.rs +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use bitflags::bitflags; - -use crate::ansi::{Color, NamedColor}; -use crate::grid::{self, GridCell}; -use crate::index::Column; - -// Maximum number of zerowidth characters which will be stored per cell. -pub const MAX_ZEROWIDTH_CHARS: usize = 5; - -bitflags! { - #[derive(Serialize, Deserialize)] - pub struct Flags: u16 { - const INVERSE = 0b00_0000_0001; - const BOLD = 0b00_0000_0010; - const ITALIC = 0b00_0000_0100; - const UNDERLINE = 0b00_0000_1000; - const WRAPLINE = 0b00_0001_0000; - const WIDE_CHAR = 0b00_0010_0000; - const WIDE_CHAR_SPACER = 0b00_0100_0000; - const DIM = 0b00_1000_0000; - const DIM_BOLD = 0b00_1000_0010; - const HIDDEN = 0b01_0000_0000; - const STRIKEOUT = 0b10_0000_0000; - } -} - -const fn default_extra() -> [char; MAX_ZEROWIDTH_CHARS] { - [' '; MAX_ZEROWIDTH_CHARS] -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -pub struct Cell { - pub c: char, - pub fg: Color, - pub bg: Color, - pub flags: Flags, - #[serde(default = "default_extra")] - pub extra: [char; MAX_ZEROWIDTH_CHARS], -} - -impl Default for Cell { - fn default() -> Cell { - Cell::new(' ', Color::Named(NamedColor::Foreground), Color::Named(NamedColor::Background)) - } -} - -impl GridCell for Cell { - #[inline] - fn is_empty(&self) -> bool { - (self.c == ' ' || self.c == '\t') - && self.extra[0] == ' ' - && self.bg == Color::Named(NamedColor::Background) - && !self - .flags - .intersects(Flags::INVERSE | Flags::UNDERLINE | Flags::STRIKEOUT | Flags::WRAPLINE) - } - - #[inline] - fn is_wrap(&self) -> bool { - self.flags.contains(Flags::WRAPLINE) - } - - #[inline] - fn set_wrap(&mut self, wrap: bool) { - if wrap { - self.flags.insert(Flags::WRAPLINE); - } else { - self.flags.remove(Flags::WRAPLINE); - } - } -} - -/// Get the length of occupied cells in a line -pub trait LineLength { - /// Calculate the occupied line length - fn line_length(&self) -> Column; -} - -impl LineLength for grid::Row<Cell> { - fn line_length(&self) -> Column { - let mut length = Column(0); - - if self[Column(self.len() - 1)].flags.contains(Flags::WRAPLINE) { - return Column(self.len()); - } - - for (index, cell) in self[..].iter().rev().enumerate() { - if cell.c != ' ' || cell.extra[0] != ' ' { - length = Column(self.len() - index); - break; - } - } - - length - } -} - -impl Cell { - #[inline] - pub fn bold(&self) -> bool { - self.flags.contains(Flags::BOLD) - } - - #[inline] - pub fn inverse(&self) -> bool { - self.flags.contains(Flags::INVERSE) - } - - #[inline] - pub fn dim(&self) -> bool { - self.flags.contains(Flags::DIM) - } - - pub fn new(c: char, fg: Color, bg: Color) -> Cell { - Cell { extra: [' '; MAX_ZEROWIDTH_CHARS], c, bg, fg, flags: Flags::empty() } - } - - #[inline] - pub fn reset(&mut self, template: &Cell) { - // memcpy template to self - *self = *template; - } - - #[inline] - pub fn chars(&self) -> [char; MAX_ZEROWIDTH_CHARS + 1] { - unsafe { - let mut chars = [std::mem::uninitialized(); MAX_ZEROWIDTH_CHARS + 1]; - std::ptr::write(&mut chars[0], self.c); - std::ptr::copy_nonoverlapping( - self.extra.as_ptr(), - chars.as_mut_ptr().offset(1), - self.extra.len(), - ); - chars - } - } - - #[inline] - pub fn push_extra(&mut self, c: char) { - for elem in self.extra.iter_mut() { - if elem == &' ' { - *elem = c; - break; - } - } - } -} - -#[cfg(test)] -mod tests { - use super::{Cell, LineLength}; - - use crate::grid::Row; - use crate::index::Column; - - #[test] - fn line_length_works() { - let template = Cell::default(); - let mut row = Row::new(Column(10), &template); - row[Column(5)].c = 'a'; - - assert_eq!(row.line_length(), Column(6)); - } - - #[test] - fn line_length_works_with_wrapline() { - let template = Cell::default(); - let mut row = Row::new(Column(10), &template); - row[Column(9)].flags.insert(super::Flags::WRAPLINE); - - assert_eq!(row.line_length(), Column(10)); - } -} - -#[cfg(all(test, feature = "bench"))] -mod benches { - extern crate test; - use super::Cell; - - #[bench] - fn cell_reset(b: &mut test::Bencher) { - b.iter(|| { - let mut cell = Cell::default(); - - for _ in 0..100 { - cell.reset(test::black_box(&Cell::default())); - } - - test::black_box(cell); - }); - } -} diff --git a/src/term/color.rs b/src/term/color.rs deleted file mode 100644 index 39def612..00000000 --- a/src/term/color.rs +++ /dev/null @@ -1,224 +0,0 @@ -use std::fmt; -use std::ops::{Index, IndexMut, Mul}; - -use crate::ansi; -use crate::config::Colors; - -pub const COUNT: usize = 270; - -pub const RED: Rgb = Rgb { r: 0xff, g: 0x0, b: 0x0 }; -pub const YELLOW: Rgb = Rgb { r: 0xff, g: 0xff, b: 0x0 }; - -#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Serialize, Deserialize)] -pub struct Rgb { - pub r: u8, - pub g: u8, - pub b: u8, -} - -// a multiply function for Rgb, as the default dim is just *2/3 -impl Mul<f32> for Rgb { - type Output = Rgb; - - fn mul(self, rhs: f32) -> Rgb { - let result = Rgb { - r: (f32::from(self.r) * rhs).max(0.0).min(255.0) as u8, - g: (f32::from(self.g) * rhs).max(0.0).min(255.0) as u8, - b: (f32::from(self.b) * rhs).max(0.0).min(255.0) as u8, - }; - - trace!("Scaling RGB by {} from {:?} to {:?}", rhs, self, result); - - result - } -} - -/// List of indexed colors -/// -/// The first 16 entries are the standard ansi named colors. Items 16..232 are -/// the color cube. Items 233..256 are the grayscale ramp. Item 256 is -/// the configured foreground color, item 257 is the configured background -/// color, item 258 is the cursor foreground color, item 259 is the cursor -/// background color. Following that are 8 positions for dim colors. -/// Item 268 is the bright foreground color, 269 the dim foreground. -#[derive(Copy, Clone)] -pub struct List([Rgb; COUNT]); - -impl<'a> From<&'a Colors> for List { - fn from(colors: &Colors) -> List { - // Type inference fails without this annotation - let mut list: List = unsafe { ::std::mem::uninitialized() }; - - list.fill_named(colors); - list.fill_cube(colors); - list.fill_gray_ramp(colors); - - list - } -} - -impl List { - pub fn fill_named(&mut self, colors: &Colors) { - // Normals - self[ansi::NamedColor::Black] = colors.normal.black; - self[ansi::NamedColor::Red] = colors.normal.red; - self[ansi::NamedColor::Green] = colors.normal.green; - self[ansi::NamedColor::Yellow] = colors.normal.yellow; - self[ansi::NamedColor::Blue] = colors.normal.blue; - self[ansi::NamedColor::Magenta] = colors.normal.magenta; - self[ansi::NamedColor::Cyan] = colors.normal.cyan; - self[ansi::NamedColor::White] = colors.normal.white; - - // Brights - self[ansi::NamedColor::BrightBlack] = colors.bright.black; - self[ansi::NamedColor::BrightRed] = colors.bright.red; - self[ansi::NamedColor::BrightGreen] = colors.bright.green; - self[ansi::NamedColor::BrightYellow] = colors.bright.yellow; - self[ansi::NamedColor::BrightBlue] = colors.bright.blue; - self[ansi::NamedColor::BrightMagenta] = colors.bright.magenta; - self[ansi::NamedColor::BrightCyan] = colors.bright.cyan; - self[ansi::NamedColor::BrightWhite] = colors.bright.white; - self[ansi::NamedColor::BrightForeground] = - colors.primary.bright_foreground.unwrap_or(colors.primary.foreground); - - // Foreground and background - self[ansi::NamedColor::Foreground] = colors.primary.foreground; - self[ansi::NamedColor::Background] = colors.primary.background; - - // Foreground and background for custom cursor colors - self[ansi::NamedColor::CursorText] = colors.cursor.text.unwrap_or_else(Rgb::default); - self[ansi::NamedColor::Cursor] = colors.cursor.cursor.unwrap_or_else(Rgb::default); - - // Dims - self[ansi::NamedColor::DimForeground] = - colors.primary.dim_foreground.unwrap_or(colors.primary.foreground * 0.66); - match colors.dim { - Some(ref dim) => { - trace!("Using config-provided dim colors"); - self[ansi::NamedColor::DimBlack] = dim.black; - self[ansi::NamedColor::DimRed] = dim.red; - self[ansi::NamedColor::DimGreen] = dim.green; - self[ansi::NamedColor::DimYellow] = dim.yellow; - self[ansi::NamedColor::DimBlue] = dim.blue; - self[ansi::NamedColor::DimMagenta] = dim.magenta; - self[ansi::NamedColor::DimCyan] = dim.cyan; - self[ansi::NamedColor::DimWhite] = dim.white; - }, - None => { - trace!("Deriving dim colors from normal colors"); - self[ansi::NamedColor::DimBlack] = colors.normal.black * 0.66; - self[ansi::NamedColor::DimRed] = colors.normal.red * 0.66; - self[ansi::NamedColor::DimGreen] = colors.normal.green * 0.66; - self[ansi::NamedColor::DimYellow] = colors.normal.yellow * 0.66; - self[ansi::NamedColor::DimBlue] = colors.normal.blue * 0.66; - self[ansi::NamedColor::DimMagenta] = colors.normal.magenta * 0.66; - self[ansi::NamedColor::DimCyan] = colors.normal.cyan * 0.66; - self[ansi::NamedColor::DimWhite] = colors.normal.white * 0.66; - }, - } - } - - pub fn fill_cube(&mut self, colors: &Colors) { - let mut index: usize = 16; - // Build colors - for r in 0..6 { - for g in 0..6 { - for b in 0..6 { - // Override colors 16..232 with the config (if present) - if let Some(indexed_color) = - colors.indexed_colors.iter().find(|ic| ic.index == index as u8) - { - self[index] = indexed_color.color; - } else { - self[index] = Rgb { - r: if r == 0 { 0 } else { r * 40 + 55 }, - b: if b == 0 { 0 } else { b * 40 + 55 }, - g: if g == 0 { 0 } else { g * 40 + 55 }, - }; - } - index += 1; - } - } - } - - debug_assert!(index == 232); - } - - pub fn fill_gray_ramp(&mut self, colors: &Colors) { - let mut index: usize = 232; - - for i in 0..24 { - // Index of the color is number of named colors + number of cube colors + i - let color_index = 16 + 216 + i; - - // Override colors 232..256 with the config (if present) - if let Some(indexed_color) = - colors.indexed_colors.iter().find(|ic| ic.index == color_index) - { - self[index] = indexed_color.color; - index += 1; - continue; - } - - let value = i * 10 + 8; - self[index] = Rgb { r: value, g: value, b: value }; - index += 1; - } - - debug_assert!(index == 256); - } -} - -impl fmt::Debug for List { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("List[..]") - } -} - -impl Index<ansi::NamedColor> for List { - type Output = Rgb; - - #[inline] - fn index(&self, idx: ansi::NamedColor) -> &Self::Output { - &self.0[idx as usize] - } -} - -impl IndexMut<ansi::NamedColor> for List { - #[inline] - fn index_mut(&mut self, idx: ansi::NamedColor) -> &mut Self::Output { - &mut self.0[idx as usize] - } -} - -impl Index<usize> for List { - type Output = Rgb; - - #[inline] - fn index(&self, idx: usize) -> &Self::Output { - &self.0[idx] - } -} - -impl IndexMut<usize> for List { - #[inline] - fn index_mut(&mut self, idx: usize) -> &mut Self::Output { - &mut self.0[idx] - } -} - -impl Index<u8> for List { - type Output = Rgb; - - #[inline] - fn index(&self, idx: u8) -> &Self::Output { - &self.0[idx as usize] - } -} - -impl IndexMut<u8> for List { - #[inline] - fn index_mut(&mut self, idx: u8) -> &mut Self::Output { - &mut self.0[idx as usize] - } -} diff --git a/src/term/mod.rs b/src/term/mod.rs deleted file mode 100644 index 94b2ade2..00000000 --- a/src/term/mod.rs +++ /dev/null @@ -1,2442 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! Exports the `Term` type which is a high-level API for the Grid -use std::cmp::{max, min}; -use std::ops::{Index, IndexMut, Range, RangeInclusive}; -use std::time::{Duration, Instant}; -use std::{io, mem, ptr}; - -use copypasta::{Clipboard, Load, Store}; -use font::{self, RasterizedGlyph, Size}; -use glutin::MouseCursor; -use unicode_width::UnicodeWidthChar; - -use crate::ansi::{ - self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset, -}; -use crate::config::{Config, VisualBellAnimation}; -use crate::cursor; -use crate::grid::{ - BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll, - ViewportPosition, -}; -use crate::index::{self, Column, Contains, IndexRange, Line, Linear, Point}; -use crate::input::FONT_SIZE_STEP; -use crate::message_bar::MessageBuffer; -use crate::selection::{self, Locations, Selection}; -use crate::term::cell::{Cell, Flags, LineLength}; -use crate::term::color::Rgb; -use crate::url::{Url, UrlParser}; - -#[cfg(windows)] -use crate::tty; - -pub mod cell; -pub mod color; - -/// A type that can expand a given point to a region -/// -/// Usually this is implemented for some 2-D array type since -/// points are two dimensional indices. -pub trait Search { - /// Find the nearest semantic boundary _to the left_ of provided point. - fn semantic_search_left(&self, _: Point<usize>) -> Point<usize>; - /// Find the nearest semantic boundary _to the point_ of provided point. - fn semantic_search_right(&self, _: Point<usize>) -> Point<usize>; - /// Find the nearest URL boundary in both directions. - fn url_search(&self, _: Point<usize>) -> Option<Url>; -} - -impl Search for Term { - fn semantic_search_left(&self, mut point: Point<usize>) -> Point<usize> { - // Limit the starting point to the last line in the history - point.line = min(point.line, self.grid.len() - 1); - - let mut iter = self.grid.iter_from(point); - let last_col = self.grid.num_cols() - Column(1); - - while let Some(cell) = iter.prev() { - if self.semantic_escape_chars.contains(cell.c) { - break; - } - - if iter.cur.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) { - break; // cut off if on new line or hit escape char - } - - point = iter.cur; - } - - point - } - - fn semantic_search_right(&self, mut point: Point<usize>) -> Point<usize> { - // Limit the starting point to the last line in the history - point.line = min(point.line, self.grid.len() - 1); - - let mut iter = self.grid.iter_from(point); - let last_col = self.grid.num_cols() - 1; - - while let Some(cell) = iter.next() { - if self.semantic_escape_chars.contains(cell.c) { - break; - } - - point = iter.cur; - - if point.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) { - break; // cut off if on new line or hit escape char - } - } - - point - } - - fn url_search(&self, mut point: Point<usize>) -> Option<Url> { - let last_col = self.grid.num_cols() - 1; - - // Switch first line from top to bottom - point.line = self.grid.num_lines().0 - point.line - 1; - - // Remove viewport scroll offset - point.line += self.grid.display_offset(); - - // Create forwards and backwards iterators - let mut iterf = self.grid.iter_from(point); - point.col += 1; - let mut iterb = self.grid.iter_from(point); - - // Find URLs - let mut url_parser = UrlParser::new(); - while let Some(cell) = iterb.prev() { - if (iterb.cur.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE)) - || url_parser.advance_left(cell) - { - break; - } - } - - while let Some(cell) = iterf.next() { - if url_parser.advance_right(cell) - || (iterf.cur.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE)) - { - break; - } - } - url_parser.url() - } -} - -impl selection::Dimensions for Term { - fn dimensions(&self) -> Point { - Point { col: self.grid.num_cols(), line: self.grid.num_lines() } - } -} - -/// Iterator that yields cells needing render -/// -/// Yields cells that require work to be displayed (that is, not a an empty -/// background cell). Additionally, this manages some state of the grid only -/// relevant for rendering like temporarily changing the cell with the cursor. -/// -/// This manages the cursor during a render. The cursor location is inverted to -/// draw it, and reverted after drawing to maintain state. -pub struct RenderableCellsIter<'a> { - inner: DisplayIter<'a, Cell>, - grid: &'a Grid<Cell>, - cursor: &'a Point, - cursor_offset: usize, - cursor_cell: Option<RasterizedGlyph>, - cursor_style: CursorStyle, - config: &'a Config, - colors: &'a color::List, - selection: Option<RangeInclusive<index::Linear>>, - url_highlight: &'a Option<RangeInclusive<index::Linear>>, -} - -impl<'a> RenderableCellsIter<'a> { - /// Create the renderable cells iterator - /// - /// The cursor and terminal mode are required for properly displaying the - /// cursor. - fn new<'b>( - term: &'b Term, - config: &'b Config, - selection: Option<Locations>, - mut cursor_style: CursorStyle, - metrics: font::Metrics, - ) -> RenderableCellsIter<'b> { - let grid = &term.grid; - - let cursor_offset = grid.line_to_offset(term.cursor.point.line); - let inner = grid.display_iter(); - - let mut selection_range = None; - if let Some(loc) = selection { - // Get on-screen lines of the selection's locations - let start_line = grid.buffer_line_to_visible(loc.start.line); - let end_line = grid.buffer_line_to_visible(loc.end.line); - - // Get start/end locations based on what part of selection is on screen - let locations = match (start_line, end_line) { - (ViewportPosition::Visible(start_line), ViewportPosition::Visible(end_line)) => { - Some((start_line, loc.start.col, end_line, loc.end.col)) - }, - (ViewportPosition::Visible(start_line), ViewportPosition::Above) => { - Some((start_line, loc.start.col, Line(0), Column(0))) - }, - (ViewportPosition::Below, ViewportPosition::Visible(end_line)) => { - Some((grid.num_lines(), Column(0), end_line, loc.end.col)) - }, - (ViewportPosition::Below, ViewportPosition::Above) => { - Some((grid.num_lines(), Column(0), Line(0), Column(0))) - }, - _ => None, - }; - - if let Some((start_line, start_col, end_line, end_col)) = locations { - // start and end *lines* are swapped as we switch from buffer to - // Line coordinates. - let mut end = Point { line: start_line, col: start_col }; - let mut start = Point { line: end_line, col: end_col }; - - if start > end { - ::std::mem::swap(&mut start, &mut end); - } - - let cols = grid.num_cols(); - let start = Linear::from_point(cols, start.into()); - let end = Linear::from_point(cols, end.into()); - - // Update the selection - selection_range = Some(RangeInclusive::new(start, end)); - } - } - - // Load cursor glyph - let cursor = &term.cursor.point; - let cursor_visible = term.mode.contains(TermMode::SHOW_CURSOR) && grid.contains(cursor); - let cursor_cell = if cursor_visible { - let offset_x = config.font().offset().x; - let offset_y = config.font().offset().y; - - let is_wide = grid[cursor].flags.contains(cell::Flags::WIDE_CHAR) - && (cursor.col + 1) < grid.num_cols(); - Some(cursor::get_cursor_glyph(cursor_style, metrics, offset_x, offset_y, is_wide)) - } else { - // Use hidden cursor so text will not get inverted - cursor_style = CursorStyle::Hidden; - None - }; - - RenderableCellsIter { - cursor, - cursor_offset, - grid, - inner, - selection: selection_range, - url_highlight: &grid.url_highlight, - config, - colors: &term.colors, - cursor_cell, - cursor_style, - } - } -} - -#[derive(Clone, Debug)] -pub enum RenderableCellContent { - Chars([char; cell::MAX_ZEROWIDTH_CHARS + 1]), - Cursor((CursorStyle, RasterizedGlyph)), -} - -#[derive(Clone, Debug)] -pub struct RenderableCell { - /// A _Display_ line (not necessarily an _Active_ line) - pub line: Line, - pub column: Column, - pub inner: RenderableCellContent, - pub fg: Rgb, - pub bg: Rgb, - pub bg_alpha: f32, - pub flags: cell::Flags, -} - -impl RenderableCell { - fn new(config: &Config, colors: &color::List, cell: Indexed<Cell>, selected: bool) -> Self { - // Lookup RGB values - let mut fg_rgb = Self::compute_fg_rgb(config, colors, cell.fg, cell.flags); - let mut bg_rgb = Self::compute_bg_rgb(colors, cell.bg); - - let selection_background = config.colors().selection.background; - if let (true, Some(col)) = (selected, selection_background) { - // Override selection background with config colors - bg_rgb = col; - } else if selected ^ cell.inverse() { - if fg_rgb == bg_rgb && !cell.flags.contains(Flags::HIDDEN) { - // Reveal inversed text when fg/bg is the same - fg_rgb = colors[NamedColor::Background]; - bg_rgb = colors[NamedColor::Foreground]; - } else { - // Invert cell fg and bg colors - mem::swap(&mut fg_rgb, &mut bg_rgb); - } - } - - // Override selection text with config colors - if let (true, Some(col)) = (selected, config.colors().selection.text) { - fg_rgb = col; - } - - RenderableCell { - line: cell.line, - column: cell.column, - inner: RenderableCellContent::Chars(cell.chars()), - fg: fg_rgb, - bg: bg_rgb, - bg_alpha: Self::compute_bg_alpha(colors, bg_rgb), - flags: cell.flags, - } - } - - fn compute_fg_rgb(config: &Config, colors: &color::List, fg: Color, flags: cell::Flags) -> Rgb { - match fg { - Color::Spec(rgb) => rgb, - Color::Named(ansi) => { - match (config.draw_bold_text_with_bright_colors(), flags & Flags::DIM_BOLD) { - // If no bright foreground is set, treat it like the BOLD flag doesn't exist - (_, cell::Flags::DIM_BOLD) - if ansi == NamedColor::Foreground - && config.colors().primary.bright_foreground.is_none() => - { - colors[NamedColor::DimForeground] - }, - // Draw bold text in bright colors *and* contains bold flag. - (true, cell::Flags::BOLD) => colors[ansi.to_bright()], - // Cell is marked as dim and not bold - (_, cell::Flags::DIM) | (false, cell::Flags::DIM_BOLD) => colors[ansi.to_dim()], - // None of the above, keep original color. - _ => colors[ansi], - } - }, - Color::Indexed(idx) => { - let idx = match ( - config.draw_bold_text_with_bright_colors(), - flags & Flags::DIM_BOLD, - idx, - ) { - (true, cell::Flags::BOLD, 0..=7) => idx as usize + 8, - (false, cell::Flags::DIM, 8..=15) => idx as usize - 8, - (false, cell::Flags::DIM, 0..=7) => idx as usize + 260, - _ => idx as usize, - }; - - colors[idx] - }, - } - } - - #[inline] - fn compute_bg_alpha(colors: &color::List, bg: Rgb) -> f32 { - if colors[NamedColor::Background] == bg { - 0. - } else { - 1. - } - } - - #[inline] - fn compute_bg_rgb(colors: &color::List, bg: Color) -> Rgb { - match bg { - Color::Spec(rgb) => rgb, - Color::Named(ansi) => colors[ansi], - Color::Indexed(idx) => colors[idx], - } - } -} - -impl<'a> Iterator for RenderableCellsIter<'a> { - type Item = RenderableCell; - - /// Gets the next renderable cell - /// - /// Skips empty (background) cells and applies any flags to the cell state - /// (eg. invert fg and bg colors). - #[inline] - fn next(&mut self) -> Option<Self::Item> { - loop { - if self.cursor_offset == self.inner.offset() && self.inner.column() == self.cursor.col { - // Handle cursor - if let Some(cursor_cell) = self.cursor_cell.take() { - let cell = Indexed { - inner: self.grid[self.cursor], - column: self.cursor.col, - line: self.cursor.line, - }; - let mut renderable_cell = - RenderableCell::new(self.config, self.colors, cell, false); - - renderable_cell.inner = - RenderableCellContent::Cursor((self.cursor_style, cursor_cell)); - - if let Some(color) = self.config.cursor_cursor_color() { - renderable_cell.fg = color; - } - - return Some(renderable_cell); - } else { - let mut cell = - RenderableCell::new(self.config, self.colors, self.inner.next()?, false); - - if self.cursor_style == CursorStyle::Block { - std::mem::swap(&mut cell.bg, &mut cell.fg); - - if let Some(color) = self.config.cursor_text_color() { - cell.fg = color; - } - } - - return Some(cell); - } - } else { - let mut cell = self.inner.next()?; - - let index = Linear::new(self.grid.num_cols(), cell.column, cell.line); - - let selected = - self.selection.as_ref().map(|range| range.contains_(index)).unwrap_or(false); - - // Skip empty cells - if cell.is_empty() && !selected { - continue; - } - - // Underline URL highlights - if self.url_highlight.as_ref().map(|range| range.contains_(index)).unwrap_or(false) - { - cell.inner.flags.insert(Flags::UNDERLINE); - } - - return Some(RenderableCell::new(self.config, self.colors, cell, selected)); - } - } - } -} - -pub mod mode { - use bitflags::bitflags; - - bitflags! { - pub struct TermMode: u16 { - const SHOW_CURSOR = 0b00_0000_0000_0001; - const APP_CURSOR = 0b00_0000_0000_0010; - const APP_KEYPAD = 0b00_0000_0000_0100; - const MOUSE_REPORT_CLICK = 0b00_0000_0000_1000; - const BRACKETED_PASTE = 0b00_0000_0001_0000; - const SGR_MOUSE = 0b00_0000_0010_0000; - const MOUSE_MOTION = 0b00_0000_0100_0000; - const LINE_WRAP = 0b00_0000_1000_0000; - const LINE_FEED_NEW_LINE = 0b00_0001_0000_0000; - const ORIGIN = 0b00_0010_0000_0000; - const INSERT = 0b00_0100_0000_0000; - const FOCUS_IN_OUT = 0b00_1000_0000_0000; - const ALT_SCREEN = 0b01_0000_0000_0000; - const MOUSE_DRAG = 0b10_0000_0000_0000; - const ANY = 0b11_1111_1111_1111; - const NONE = 0; - } - } - - impl Default for TermMode { - fn default() -> TermMode { - TermMode::SHOW_CURSOR | TermMode::LINE_WRAP - } - } -} - -pub use crate::term::mode::TermMode; - -trait CharsetMapping { - fn map(&self, c: char) -> char { - c - } -} - -impl CharsetMapping for StandardCharset { - /// Switch/Map character to the active charset. Ascii is the common case and - /// for that we want to do as little as possible. - #[inline] - fn map(&self, c: char) -> char { - match *self { - StandardCharset::Ascii => c, - StandardCharset::SpecialCharacterAndLineDrawing => match c { - '`' => '◆', - 'a' => '▒', - 'b' => '\t', - 'c' => '\u{000c}', - 'd' => '\r', - 'e' => '\n', - 'f' => '°', - 'g' => '±', - 'h' => '\u{2424}', - 'i' => '\u{000b}', - 'j' => '┘', - 'k' => '┐', - 'l' => '┌', - 'm' => '└', - 'n' => '┼', - 'o' => '⎺', - 'p' => '⎻', - 'q' => '─', - 'r' => '⎼', - 's' => '⎽', - 't' => '├', - 'u' => '┤', - 'v' => '┴', - 'w' => '┬', - 'x' => '│', - 'y' => '≤', - 'z' => '≥', - '{' => 'π', - '|' => '≠', - '}' => '£', - '~' => '·', - _ => c, - }, - } - } -} - -#[derive(Default, Copy, Clone)] -struct Charsets([StandardCharset; 4]); - -impl Index<CharsetIndex> for Charsets { - type Output = StandardCharset; - - fn index(&self, index: CharsetIndex) -> &StandardCharset { - &self.0[index as usize] - } -} - -impl IndexMut<CharsetIndex> for Charsets { - fn index_mut(&mut self, index: CharsetIndex) -> &mut StandardCharset { - &mut self.0[index as usize] - } -} - -#[derive(Default, Copy, Clone)] -pub struct Cursor { - /// The location of this cursor - pub point: Point, - - /// Template cell when using this cursor - template: Cell, - - /// Currently configured graphic character sets - charsets: Charsets, -} - -pub struct VisualBell { - /// Visual bell animation - animation: VisualBellAnimation, - - /// Visual bell duration - duration: Duration, - - /// The last time the visual bell rang, if at all - start_time: Option<Instant>, -} - -fn cubic_bezier(p0: f64, p1: f64, p2: f64, p3: f64, x: f64) -> f64 { - (1.0 - x).powi(3) * p0 - + 3.0 * (1.0 - x).powi(2) * x * p1 - + 3.0 * (1.0 - x) * x.powi(2) * p2 - + x.powi(3) * p3 -} - -impl VisualBell { - pub fn new(config: &Config) -> VisualBell { - let visual_bell_config = config.visual_bell(); - VisualBell { - animation: visual_bell_config.animation(), - duration: visual_bell_config.duration(), - start_time: None, - } - } - - /// Ring the visual bell, and return its intensity. - pub fn ring(&mut self) -> f64 { - let now = Instant::now(); - self.start_time = Some(now); - self.intensity_at_instant(now) - } - - /// Get the currently intensity of the visual bell. The bell's intensity - /// ramps down from 1.0 to 0.0 at a rate determined by the bell's duration. - pub fn intensity(&self) -> f64 { - self.intensity_at_instant(Instant::now()) - } - - /// Check whether or not the visual bell has completed "ringing". - pub fn completed(&mut self) -> bool { - match self.start_time { - Some(earlier) => { - if Instant::now().duration_since(earlier) >= self.duration { - self.start_time = None; - } - false - }, - None => true, - } - } - - /// Get the intensity of the visual bell at a particular instant. The bell's - /// intensity ramps down from 1.0 to 0.0 at a rate determined by the bell's - /// duration. - pub fn intensity_at_instant(&self, instant: Instant) -> f64 { - // If `duration` is zero, then the VisualBell is disabled; therefore, - // its `intensity` is zero. - if self.duration == Duration::from_secs(0) { - return 0.0; - } - - match self.start_time { - // Similarly, if `start_time` is `None`, then the VisualBell has not - // been "rung"; therefore, its `intensity` is zero. - None => 0.0, - - Some(earlier) => { - // Finally, if the `instant` at which we wish to compute the - // VisualBell's `intensity` occurred before the VisualBell was - // "rung", then its `intensity` is also zero. - if instant < earlier { - return 0.0; - } - - let elapsed = instant.duration_since(earlier); - let elapsed_f = - elapsed.as_secs() as f64 + f64::from(elapsed.subsec_nanos()) / 1e9f64; - let duration_f = self.duration.as_secs() as f64 - + f64::from(self.duration.subsec_nanos()) / 1e9f64; - - // Otherwise, we compute a value `time` from 0.0 to 1.0 - // inclusive that represents the ratio of `elapsed` time to the - // `duration` of the VisualBell. - let time = (elapsed_f / duration_f).min(1.0); - - // We use this to compute the inverse `intensity` of the - // VisualBell. When `time` is 0.0, `inverse_intensity` is 0.0, - // and when `time` is 1.0, `inverse_intensity` is 1.0. - let inverse_intensity = match self.animation { - VisualBellAnimation::Ease | VisualBellAnimation::EaseOut => { - cubic_bezier(0.25, 0.1, 0.25, 1.0, time) - }, - VisualBellAnimation::EaseOutSine => cubic_bezier(0.39, 0.575, 0.565, 1.0, time), - VisualBellAnimation::EaseOutQuad => cubic_bezier(0.25, 0.46, 0.45, 0.94, time), - VisualBellAnimation::EaseOutCubic => { - cubic_bezier(0.215, 0.61, 0.355, 1.0, time) - }, - VisualBellAnimation::EaseOutQuart => cubic_bezier(0.165, 0.84, 0.44, 1.0, time), - VisualBellAnimation::EaseOutQuint => cubic_bezier(0.23, 1.0, 0.32, 1.0, time), - VisualBellAnimation::EaseOutExpo => cubic_bezier(0.19, 1.0, 0.22, 1.0, time), - VisualBellAnimation::EaseOutCirc => cubic_bezier(0.075, 0.82, 0.165, 1.0, time), - VisualBellAnimation::Linear => time, - }; - - // Since we want the `intensity` of the VisualBell to decay over - // `time`, we subtract the `inverse_intensity` from 1.0. - 1.0 - inverse_intensity - }, - } - } - - pub fn update_config(&mut self, config: &Config) { - let visual_bell_config = config.visual_bell(); - self.animation = visual_bell_config.animation(); - self.duration = visual_bell_config.duration(); - } -} - -pub struct Term { - /// The grid - grid: Grid<Cell>, - - /// Tracks if the next call to input will need to first handle wrapping. - /// This is true after the last column is set with the input function. Any function that - /// implicitly sets the line or column needs to set this to false to avoid wrapping twice. - /// input_needs_wrap ensures that cursor.col is always valid for use into indexing into - /// arrays. Without it we would have to sanitize cursor.col every time we used it. - input_needs_wrap: bool, - - /// Got a request to set title; it's buffered here until next draw. - /// - /// Would be nice to avoid the allocation... - next_title: Option<String>, - - /// Got a request to set the mouse cursor; it's buffered here until the next draw - next_mouse_cursor: Option<MouseCursor>, - - /// Alternate grid - alt_grid: Grid<Cell>, - - /// Alt is active - alt: bool, - - /// The cursor - cursor: Cursor, - - /// The graphic character set, out of `charsets`, which ASCII is currently - /// being mapped to - active_charset: CharsetIndex, - - /// Tabstops - tabs: TabStops, - - /// Mode flags - mode: TermMode, - - /// Scroll region - scroll_region: Range<Line>, - - /// Font size - pub font_size: Size, - original_font_size: Size, - - /// Size - size_info: SizeInfo, - - pub dirty: bool, - - pub visual_bell: VisualBell, - pub next_is_urgent: Option<bool>, - - /// Saved cursor from main grid - cursor_save: Cursor, - - /// Saved cursor from alt grid - cursor_save_alt: Cursor, - - semantic_escape_chars: String, - - /// Colors used for rendering - colors: color::List, - - /// Is color in `colors` modified or not - color_modified: [bool; color::COUNT], - - /// Original colors from config - original_colors: color::List, - - /// Current style of the cursor - cursor_style: Option<CursorStyle>, - - /// Default style for resetting the cursor - default_cursor_style: CursorStyle, - - /// Whether to permit updating the terminal title - dynamic_title: bool, - - /// Number of spaces in one tab - tabspaces: usize, - - /// Automatically scroll to bottom when new lines are added - auto_scroll: bool, - - /// Buffer to store messages for the message bar - message_buffer: MessageBuffer, - - /// Hint that Alacritty should be closed - should_exit: bool, -} - -/// Terminal size info -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] -pub struct SizeInfo { - /// Terminal window width - pub width: f32, - - /// Terminal window height - pub height: f32, - - /// Width of individual cell - pub cell_width: f32, - - /// Height of individual cell - pub cell_height: f32, - - /// Horizontal window padding - pub padding_x: f32, - - /// Horizontal window padding - pub padding_y: f32, - - /// DPI factor of the current window - #[serde(default)] - pub dpr: f64, -} - -impl SizeInfo { - #[inline] - pub fn lines(&self) -> Line { - Line(((self.height - 2. * self.padding_y) / self.cell_height) as usize) - } - - #[inline] - pub fn cols(&self) -> Column { - Column(((self.width - 2. * self.padding_x) / self.cell_width) as usize) - } - - pub fn contains_point(&self, x: usize, y: usize, include_padding: bool) -> bool { - if include_padding { - x < self.width as usize && y < self.height as usize - } else { - x < (self.width - self.padding_x) as usize - && x >= self.padding_x as usize - && y < (self.height - self.padding_y) as usize - && y >= self.padding_y as usize - } - } - - pub fn pixels_to_coords(&self, x: usize, y: usize) -> Point { - let col = Column(x.saturating_sub(self.padding_x as usize) / (self.cell_width as usize)); - let line = Line(y.saturating_sub(self.padding_y as usize) / (self.cell_height as usize)); - - Point { - line: min(line, Line(self.lines().saturating_sub(1))), - col: min(col, Column(self.cols().saturating_sub(1))), - } - } -} - -impl Term { - pub fn selection(&self) -> &Option<Selection> { - &self.grid.selection - } - - pub fn selection_mut(&mut self) -> &mut Option<Selection> { - &mut self.grid.selection - } - - #[inline] - pub fn get_next_title(&mut self) -> Option<String> { - self.next_title.take() - } - - #[inline] - pub fn scroll_display(&mut self, scroll: Scroll) { - self.grid.scroll_display(scroll); - self.reset_url_highlight(); - self.dirty = true; - } - - #[inline] - pub fn get_next_mouse_cursor(&mut self) -> Option<MouseCursor> { - self.next_mouse_cursor.take() - } - - pub fn new(config: &Config, size: SizeInfo, message_buffer: MessageBuffer) -> Term { - let num_cols = size.cols(); - let num_lines = size.lines(); - - let history_size = config.scrolling().history as usize; - let grid = Grid::new(num_lines, num_cols, history_size, Cell::default()); - let alt = Grid::new(num_lines, num_cols, 0 /* scroll history */, Cell::default()); - - let tabspaces = config.tabspaces(); - let tabs = TabStops::new(grid.num_cols(), tabspaces); - - let scroll_region = Line(0)..grid.num_lines(); - - let colors = color::List::from(config.colors()); - - Term { - next_title: None, - next_mouse_cursor: None, - dirty: false, - visual_bell: VisualBell::new(config), - next_is_urgent: None, - input_needs_wrap: false, - grid, - alt_grid: alt, - alt: false, - font_size: config.font().size(), - original_font_size: config.font().size(), - active_charset: Default::default(), - cursor: Default::default(), - cursor_save: Default::default(), - cursor_save_alt: Default::default(), - tabs, - mode: Default::default(), - scroll_region, - size_info: size, - colors, - color_modified: [false; color::COUNT], - original_colors: colors, - semantic_escape_chars: config.selection().semantic_escape_chars.clone(), - cursor_style: None, - default_cursor_style: config.cursor_style(), - dynamic_title: config.dynamic_title(), - tabspaces, - auto_scroll: config.scrolling().auto_scroll, - message_buffer, - should_exit: false, - } - } - - pub fn change_font_size(&mut self, delta: f32) { - // Saturating addition with minimum font size FONT_SIZE_STEP - let new_size = self.font_size + Size::new(delta); - self.font_size = max(new_size, Size::new(FONT_SIZE_STEP)); - self.dirty = true; - } - - pub fn reset_font_size(&mut self) { - self.font_size = self.original_font_size; - self.dirty = true; - } - - pub fn update_config(&mut self, config: &Config) { - self.semantic_escape_chars = config.selection().semantic_escape_chars.clone(); - self.original_colors.fill_named(config.colors()); - self.original_colors.fill_cube(config.colors()); - self.original_colors.fill_gray_ramp(config.colors()); - for i in 0..color::COUNT { - if !self.color_modified[i] { - self.colors[i] = self.original_colors[i]; - } - } - self.visual_bell.update_config(config); - self.default_cursor_style = config.cursor_style(); - self.dynamic_title = config.dynamic_title(); - self.auto_scroll = config.scrolling().auto_scroll; - self.grid.update_history(config.scrolling().history as usize, &self.cursor.template); - } - - #[inline] - pub fn needs_draw(&self) -> bool { - self.dirty - } - - pub fn selection_to_string(&self) -> Option<String> { - /// Need a generic push() for the Append trait - trait PushChar { - fn push_char(&mut self, c: char); - fn maybe_newline(&mut self, grid: &Grid<Cell>, line: usize, ending: Column) { - if ending != Column(0) - && !grid[line][ending - 1].flags.contains(cell::Flags::WRAPLINE) - { - self.push_char('\n'); - } - } - } - - impl PushChar for String { - #[inline] - fn push_char(&mut self, c: char) { - self.push(c); - } - } - - trait Append: PushChar { - fn append( - &mut self, - grid: &Grid<Cell>, - tabs: &TabStops, - line: usize, - cols: Range<Column>, - ); - } - - impl Append for String { - fn append( - &mut self, - grid: &Grid<Cell>, - tabs: &TabStops, - mut line: usize, - cols: Range<Column>, - ) { - // Select until last line still within the buffer - line = min(line, grid.len() - 1); - - let grid_line = &grid[line]; - let line_length = grid_line.line_length(); - let line_end = min(line_length, cols.end + 1); - - if line_end.0 == 0 && cols.end >= grid.num_cols() - 1 { - self.push('\n'); - } else if cols.start < line_end { - let mut tab_mode = false; - - for col in IndexRange::from(cols.start..line_end) { - let cell = grid_line[col]; - - if tab_mode { - // Skip over whitespace until next tab-stop once a tab was found - if tabs[col] { - tab_mode = false; - } else if cell.c == ' ' { - continue; - } - } - - if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) { - self.push(cell.c); - for c in (&cell.chars()[1..]).iter().filter(|c| **c != ' ') { - self.push(*c); - } - } - - if cell.c == '\t' { - tab_mode = true; - } - } - - if cols.end >= grid.num_cols() - 1 { - self.maybe_newline(grid, line, line_end); - } - } - } - } - - let alt_screen = self.mode.contains(TermMode::ALT_SCREEN); - let selection = self.grid.selection.clone()?; - let span = selection.to_span(self, alt_screen)?; - - let mut res = String::new(); - - let Locations { mut start, mut end } = span.to_locations(); - - if start > end { - ::std::mem::swap(&mut start, &mut end); - } - - let line_count = end.line - start.line; - let max_col = Column(usize::max_value() - 1); - - match line_count { - // Selection within single line - 0 => { - res.append(&self.grid, &self.tabs, start.line, start.col..end.col); - }, - - // Selection ends on line following start - 1 => { - // Ending line - res.append(&self.grid, &self.tabs, end.line, end.col..max_col); - - // Starting line - res.append(&self.grid, &self.tabs, start.line, Column(0)..start.col); - }, - - // Multi line selection - _ => { - // Ending line - res.append(&self.grid, &self.tabs, end.line, end.col..max_col); - - let middle_range = (start.line + 1)..(end.line); - for line in middle_range.rev() { - res.append(&self.grid, &self.tabs, line, Column(0)..max_col); - } - - // Starting line - res.append(&self.grid, &self.tabs, start.line, Column(0)..start.col); - }, - } - - Some(res) - } - - pub(crate) fn visible_to_buffer(&self, point: Point) -> Point<usize> { - self.grid.visible_to_buffer(point) - } - - /// Convert the given pixel values to a grid coordinate - /// - /// The mouse coordinates are expected to be relative to the top left. The - /// line and column returned are also relative to the top left. - /// - /// Returns None if the coordinates are outside the window, - /// padding pixels are considered inside the window - pub fn pixels_to_coords(&self, x: usize, y: usize) -> Option<Point> { - if self.size_info.contains_point(x, y, true) { - Some(self.size_info.pixels_to_coords(x, y)) - } else { - None - } - } - - /// Access to the raw grid data structure - /// - /// This is a bit of a hack; when the window is closed, the event processor - /// serializes the grid state to a file. - pub fn grid(&self) -> &Grid<Cell> { - &self.grid - } - - /// Mutable access for swapping out the grid during tests - #[cfg(test)] - pub fn grid_mut(&mut self) -> &mut Grid<Cell> { - &mut self.grid - } - - /// Iterate over the *renderable* cells in the terminal - /// - /// A renderable cell is any cell which has content other than the default - /// background color. Cells with an alternate background color are - /// considered renderable as are cells with any text content. - pub fn renderable_cells<'b>( - &'b self, - config: &'b Config, - window_focused: bool, - metrics: font::Metrics, - ) -> RenderableCellsIter<'_> { - let alt_screen = self.mode.contains(TermMode::ALT_SCREEN); - let selection = self - .grid - .selection - .as_ref() - .and_then(|s| s.to_span(self, alt_screen)) - .map(|span| span.to_locations()); - - let cursor = if window_focused || !config.unfocused_hollow_cursor() { - self.cursor_style.unwrap_or(self.default_cursor_style) - } else { - CursorStyle::HollowBlock - }; - - RenderableCellsIter::new(&self, config, selection, cursor, metrics) - } - - /// Resize terminal to new dimensions - pub fn resize(&mut self, size: &SizeInfo) { - debug!("Resizing terminal"); - - // Bounds check; lots of math assumes width and height are > 0 - if size.width as usize <= 2 * self.size_info.padding_x as usize - || size.height as usize <= 2 * self.size_info.padding_y as usize - { - return; - } - - let old_cols = self.grid.num_cols(); - let old_lines = self.grid.num_lines(); - let mut num_cols = size.cols(); - let mut num_lines = size.lines(); - - if let Some(message) = self.message_buffer.message() { - num_lines -= message.text(size).len(); - } - - self.size_info = *size; - - if old_cols == num_cols && old_lines == num_lines { - debug!("Term::resize dimensions unchanged"); - return; - } - - self.grid.selection = None; - self.alt_grid.selection = None; - self.grid.url_highlight = None; - - // Should not allow less than 1 col, causes all sorts of checks to be required. - if num_cols <= Column(1) { - num_cols = Column(2); - } - - // Should not allow less than 1 line, causes all sorts of checks to be required. - if num_lines <= Line(1) { - num_lines = Line(2); - } - - // Scroll up to keep cursor in terminal - if self.cursor.point.line >= num_lines { - let lines = self.cursor.point.line - num_lines + 1; - self.grid.scroll_up(&(Line(0)..old_lines), lines, &self.cursor.template); - } - - // Scroll up alt grid as well - if self.cursor_save_alt.point.line >= num_lines { - let lines = self.cursor_save_alt.point.line - num_lines + 1; - self.alt_grid.scroll_up(&(Line(0)..old_lines), lines, &self.cursor_save_alt.template); - } - - // Move prompt down when growing if scrollback lines are available - if num_lines > old_lines { - if self.mode.contains(TermMode::ALT_SCREEN) { - let growage = min(num_lines - old_lines, Line(self.alt_grid.scroll_limit())); - self.cursor_save.point.line += growage; - } else { - let growage = min(num_lines - old_lines, Line(self.grid.scroll_limit())); - self.cursor.point.line += growage; - } - } - - debug!("New num_cols is {} and num_lines is {}", num_cols, num_lines); - - // Resize grids to new size - let alt_cursor_point = if self.mode.contains(TermMode::ALT_SCREEN) { - &mut self.cursor_save.point - } else { - &mut self.cursor_save_alt.point - }; - self.grid.resize(num_lines, num_cols, &mut self.cursor.point, &Cell::default()); - self.alt_grid.resize(num_lines, num_cols, alt_cursor_point, &Cell::default()); - - // Reset scrolling region to new size - self.scroll_region = Line(0)..self.grid.num_lines(); - - // Ensure cursors are in-bounds. - self.cursor.point.col = min(self.cursor.point.col, num_cols - 1); - self.cursor.point.line = min(self.cursor.point.line, num_lines - 1); - self.cursor_save.point.col = min(self.cursor_save.point.col, num_cols - 1); - self.cursor_save.point.line = min(self.cursor_save.point.line, num_lines - 1); - self.cursor_save_alt.point.col = min(self.cursor_save_alt.point.col, num_cols - 1); - self.cursor_save_alt.point.line = min(self.cursor_save_alt.point.line, num_lines - 1); - - // Recreate tabs list - self.tabs = TabStops::new(self.grid.num_cols(), self.tabspaces); - } - - #[inline] - pub fn size_info(&self) -> &SizeInfo { - &self.size_info - } - - #[inline] - pub fn mode(&self) -> &TermMode { - &self.mode - } - - #[inline] - pub fn cursor(&self) -> &Cursor { - &self.cursor - } - - pub fn swap_alt(&mut self) { - if self.alt { - let template = &self.cursor.template; - self.grid.region_mut(..).each(|c| c.reset(template)); - } - - self.alt = !self.alt; - ::std::mem::swap(&mut self.grid, &mut self.alt_grid); - } - - /// Scroll screen down - /// - /// Text moves down; clear at bottom - /// Expects origin to be in scroll range. - #[inline] - fn scroll_down_relative(&mut self, origin: Line, mut lines: Line) { - trace!("Scrolling down relative: origin={}, lines={}", origin, lines); - lines = min(lines, self.scroll_region.end - self.scroll_region.start); - lines = min(lines, self.scroll_region.end - origin); - - // Scroll between origin and bottom - let mut template = self.cursor.template; - template.flags = Flags::empty(); - self.grid.scroll_down(&(origin..self.scroll_region.end), lines, &template); - } - - /// Scroll screen up - /// - /// Text moves up; clear at top - /// Expects origin to be in scroll range. - #[inline] - fn scroll_up_relative(&mut self, origin: Line, lines: Line) { - trace!("Scrolling up relative: origin={}, lines={}", origin, lines); - let lines = min(lines, self.scroll_region.end - self.scroll_region.start); - - // Scroll from origin to bottom less number of lines - let mut template = self.cursor.template; - template.flags = Flags::empty(); - self.grid.scroll_up(&(origin..self.scroll_region.end), lines, &template); - } - - fn deccolm(&mut self) { - // Setting 132 column font makes no sense, but run the other side effects - // Clear scrolling region - let scroll_region = Line(0)..self.grid.num_lines(); - self.set_scrolling_region(scroll_region); - - // Clear grid - let template = self.cursor.template; - self.grid.region_mut(..).each(|c| c.reset(&template)); - } - - #[inline] - pub fn background_color(&self) -> Rgb { - self.colors[NamedColor::Background] - } - - #[inline] - pub fn message_buffer_mut(&mut self) -> &mut MessageBuffer { - &mut self.message_buffer - } - - #[inline] - pub fn message_buffer(&self) -> &MessageBuffer { - &self.message_buffer - } - - #[inline] - pub fn exit(&mut self) { - self.should_exit = true; - } - - #[inline] - pub fn should_exit(&self) -> bool { - self.should_exit - } - - #[inline] - pub fn set_url_highlight(&mut self, hl: RangeInclusive<index::Linear>) { - self.grid.url_highlight = Some(hl); - } - - #[inline] - pub fn reset_url_highlight(&mut self) { - let mouse_mode = - TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG | TermMode::MOUSE_REPORT_CLICK; - let mouse_cursor = if self.mode().intersects(mouse_mode) { - MouseCursor::Default - } else { - MouseCursor::Text - }; - self.set_mouse_cursor(mouse_cursor); - - self.grid.url_highlight = None; - self.dirty = true; - } -} - -impl ansi::TermInfo for Term { - #[inline] - fn lines(&self) -> Line { - self.grid.num_lines() - } - - #[inline] - fn cols(&self) -> Column { - self.grid.num_cols() - } -} - -impl ansi::Handler for Term { - /// Set the window title - #[inline] - fn set_title(&mut self, title: &str) { - if self.dynamic_title { - self.next_title = Some(title.to_owned()); - - #[cfg(windows)] - { - // cmd.exe in winpty: winpty incorrectly sets the title to ' ' instead of - // 'Alacritty' - thus we have to substitute this back to get equivalent - // behaviour as conpty. - // - // The starts_with check is necessary because other shells e.g. bash set a - // different title and don't need Alacritty prepended. - if !tty::is_conpty() && title.starts_with(' ') { - self.next_title = Some(format!("Alacritty {}", title.trim())); - } - } - } - } - - /// Set the mouse cursor - #[inline] - fn set_mouse_cursor(&mut self, cursor: MouseCursor) { - self.next_mouse_cursor = Some(cursor); - } - - /// A character to be displayed - #[inline] - fn input(&mut self, c: char) { - // If enabled, scroll to bottom when character is received - if self.auto_scroll { - self.scroll_display(Scroll::Bottom); - } - - if self.input_needs_wrap { - if !self.mode.contains(TermMode::LINE_WRAP) { - return; - } - - trace!("Wrapping input"); - - { - let location = Point { line: self.cursor.point.line, col: self.cursor.point.col }; - - let cell = &mut self.grid[&location]; - cell.flags.insert(cell::Flags::WRAPLINE); - } - - if (self.cursor.point.line + 1) >= self.scroll_region.end { - self.linefeed(); - } else { - self.cursor.point.line += 1; - } - - self.cursor.point.col = Column(0); - self.input_needs_wrap = false; - } - - // Number of cells the char will occupy - if let Some(width) = c.width() { - let num_cols = self.grid.num_cols(); - - // If in insert mode, first shift cells to the right. - if self.mode.contains(TermMode::INSERT) && self.cursor.point.col + width < num_cols { - let line = self.cursor.point.line; - let col = self.cursor.point.col; - let line = &mut self.grid[line]; - - let src = line[col..].as_ptr(); - let dst = line[(col + width)..].as_mut_ptr(); - unsafe { - // memmove - ptr::copy(src, dst, (num_cols - col - width).0); - } - } - - // Handle zero-width characters - if width == 0 { - let mut col = self.cursor.point.col.0.saturating_sub(1); - let line = self.cursor.point.line; - if self.grid[line][Column(col)].flags.contains(cell::Flags::WIDE_CHAR_SPACER) { - col = col.saturating_sub(1); - } - self.grid[line][Column(col)].push_extra(c); - return; - } - - let cell = &mut self.grid[&self.cursor.point]; - *cell = self.cursor.template; - cell.c = self.cursor.charsets[self.active_charset].map(c); - - // Handle wide chars - if width == 2 { - cell.flags.insert(cell::Flags::WIDE_CHAR); - - if self.cursor.point.col + 1 < num_cols { - self.cursor.point.col += 1; - let spacer = &mut self.grid[&self.cursor.point]; - *spacer = self.cursor.template; - spacer.flags.insert(cell::Flags::WIDE_CHAR_SPACER); - } - } - } - - if (self.cursor.point.col + 1) < self.grid.num_cols() { - self.cursor.point.col += 1; - } else { - self.input_needs_wrap = true; - } - } - - #[inline] - fn dectest(&mut self) { - trace!("Dectesting"); - let mut template = self.cursor.template; - template.c = 'E'; - - self.grid.region_mut(..).each(|c| c.reset(&template)); - } - - #[inline] - fn goto(&mut self, line: Line, col: Column) { - trace!("Going to: line={}, col={}", line, col); - let (y_offset, max_y) = if self.mode.contains(TermMode::ORIGIN) { - (self.scroll_region.start, self.scroll_region.end - 1) - } else { - (Line(0), self.grid.num_lines() - 1) - }; - - self.cursor.point.line = min(line + y_offset, max_y); - self.cursor.point.col = min(col, self.grid.num_cols() - 1); - self.input_needs_wrap = false; - } - - #[inline] - fn goto_line(&mut self, line: Line) { - trace!("Going to line: {}", line); - self.goto(line, self.cursor.point.col) - } - - #[inline] - fn goto_col(&mut self, col: Column) { - trace!("Going to column: {}", col); - self.goto(self.cursor.point.line, col) - } - - #[inline] - fn insert_blank(&mut self, count: Column) { - // Ensure inserting within terminal bounds - - let count = min(count, self.size_info.cols() - self.cursor.point.col); - - let source = self.cursor.point.col; - let destination = self.cursor.point.col + count; - let num_cells = (self.size_info.cols() - destination).0; - - let line = &mut self.grid[self.cursor.point.line]; - - unsafe { - let src = line[source..].as_ptr(); - let dst = line[destination..].as_mut_ptr(); - - ptr::copy(src, dst, num_cells); - } - - // Cells were just moved out towards the end of the line; fill in - // between source and dest with blanks. - let template = self.cursor.template; - for c in &mut line[source..destination] { - c.reset(&template); - } - } - - #[inline] - fn move_up(&mut self, lines: Line) { - trace!("Moving up: {}", lines); - let move_to = Line(self.cursor.point.line.0.saturating_sub(lines.0)); - self.goto(move_to, self.cursor.point.col) - } - - #[inline] - fn move_down(&mut self, lines: Line) { - trace!("Moving down: {}", lines); - let move_to = self.cursor.point.line + lines; - self.goto(move_to, self.cursor.point.col) - } - - #[inline] - fn move_forward(&mut self, cols: Column) { - trace!("Moving forward: {}", cols); - self.cursor.point.col = min(self.cursor.point.col + cols, self.grid.num_cols() - 1); - self.input_needs_wrap = false; - } - - #[inline] - fn move_backward(&mut self, cols: Column) { - trace!("Moving backward: {}", cols); - self.cursor.point.col -= min(self.cursor.point.col, cols); - self.input_needs_wrap = false; - } - - #[inline] - fn identify_terminal<W: io::Write>(&mut self, writer: &mut W) { - let _ = writer.write_all(b"\x1b[?6c"); - } - - #[inline] - fn device_status<W: io::Write>(&mut self, writer: &mut W, arg: usize) { - trace!("Reporting device status: {}", arg); - match arg { - 5 => { - let _ = writer.write_all(b"\x1b[0n"); - }, - 6 => { - let pos = self.cursor.point; - let _ = write!(writer, "\x1b[{};{}R", pos.line + 1, pos.col + 1); - }, - _ => debug!("unknown device status query: {}", arg), - }; - } - - #[inline] - fn move_down_and_cr(&mut self, lines: Line) { - trace!("Moving down and cr: {}", lines); - let move_to = self.cursor.point.line + lines; - self.goto(move_to, Column(0)) - } - - #[inline] - fn move_up_and_cr(&mut self, lines: Line) { - trace!("Moving up and cr: {}", lines); - let move_to = Line(self.cursor.point.line.0.saturating_sub(lines.0)); - self.goto(move_to, Column(0)) - } - - #[inline] - fn put_tab(&mut self, mut count: i64) { - trace!("Putting tab: {}", count); - - while self.cursor.point.col < self.grid.num_cols() && count != 0 { - count -= 1; - - let cell = &mut self.grid[&self.cursor.point]; - if cell.c == ' ' { - cell.c = self.cursor.charsets[self.active_charset].map('\t'); - } - - loop { - if (self.cursor.point.col + 1) == self.grid.num_cols() { - break; - } - - self.cursor.point.col += 1; - - if self.tabs[self.cursor.point.col] { - break; - } - } - } - - self.input_needs_wrap = false; - } - - /// Backspace `count` characters - #[inline] - fn backspace(&mut self) { - trace!("Backspace"); - if self.cursor.point.col > Column(0) { - self.cursor.point.col -= 1; - self.input_needs_wrap = false; - } - } - - /// Carriage return - #[inline] - fn carriage_return(&mut self) { - trace!("Carriage return"); - self.cursor.point.col = Column(0); - self.input_needs_wrap = false; - } - - /// Linefeed - #[inline] - fn linefeed(&mut self) { - trace!("Linefeed"); - let next = self.cursor.point.line + 1; - if next == self.scroll_region.end { - self.scroll_up(Line(1)); - } else if next < self.grid.num_lines() { - self.cursor.point.line += 1; - } - } - - /// Set current position as a tabstop - #[inline] - fn bell(&mut self) { - trace!("Bell"); - self.visual_bell.ring(); - self.next_is_urgent = Some(true); - } - - #[inline] - fn substitute(&mut self) { - trace!("[unimplemented] Substitute"); - } - - /// Run LF/NL - /// - /// LF/NL mode has some interesting history. According to ECMA-48 4th - /// edition, in LINE FEED mode, - /// - /// > The execution of the formatter functions LINE FEED (LF), FORM FEED - /// (FF), LINE TABULATION (VT) cause only movement of the active position in - /// the direction of the line progression. - /// - /// In NEW LINE mode, - /// - /// > The execution of the formatter functions LINE FEED (LF), FORM FEED - /// (FF), LINE TABULATION (VT) cause movement to the line home position on - /// the following line, the following form, etc. In the case of LF this is - /// referred to as the New Line (NL) option. - /// - /// Additionally, ECMA-48 4th edition says that this option is deprecated. - /// ECMA-48 5th edition only mentions this option (without explanation) - /// saying that it's been removed. - /// - /// As an emulator, we need to support it since applications may still rely - /// on it. - #[inline] - fn newline(&mut self) { - self.linefeed(); - - if self.mode.contains(TermMode::LINE_FEED_NEW_LINE) { - self.carriage_return(); - } - } - - #[inline] - fn set_horizontal_tabstop(&mut self) { - trace!("Setting horizontal tabstop"); - let column = self.cursor.point.col; - self.tabs[column] = true; - } - - #[inline] - fn scroll_up(&mut self, lines: Line) { - let origin = self.scroll_region.start; - self.scroll_up_relative(origin, lines); - } - - #[inline] - fn scroll_down(&mut self, lines: Line) { - let origin = self.scroll_region.start; - self.scroll_down_relative(origin, lines); - } - - #[inline] - fn insert_blank_lines(&mut self, lines: Line) { - trace!("Inserting blank {} lines", lines); - if self.scroll_region.contains_(self.cursor.point.line) { - let origin = self.cursor.point.line; - self.scroll_down_relative(origin, lines); - } - } - - #[inline] - fn delete_lines(&mut self, lines: Line) { - trace!("Deleting {} lines", lines); - if self.scroll_region.contains_(self.cursor.point.line) { - let origin = self.cursor.point.line; - self.scroll_up_relative(origin, lines); - } - } - - #[inline] - fn erase_chars(&mut self, count: Column) { - trace!("Erasing chars: count={}, col={}", count, self.cursor.point.col); - let start = self.cursor.point.col; - let end = min(start + count, self.grid.num_cols()); - - let row = &mut self.grid[self.cursor.point.line]; - let template = self.cursor.template; // Cleared cells have current background color set - for c in &mut row[start..end] { - c.reset(&template); - } - } - - #[inline] - fn delete_chars(&mut self, count: Column) { - // Ensure deleting within terminal bounds - let count = min(count, self.size_info.cols()); - - let start = self.cursor.point.col; - let end = min(start + count, self.grid.num_cols() - 1); - let n = (self.size_info.cols() - end).0; - - let line = &mut self.grid[self.cursor.point.line]; - - unsafe { - let src = line[end..].as_ptr(); - let dst = line[start..].as_mut_ptr(); - - ptr::copy(src, dst, n); - } - - // Clear last `count` cells in line. If deleting 1 char, need to delete - // 1 cell. - let template = self.cursor.template; - let end = self.size_info.cols() - count; - for c in &mut line[end..] { - c.reset(&template); - } - } - - #[inline] - fn move_backward_tabs(&mut self, count: i64) { - trace!("Moving backward {} tabs", count); - - for _ in 0..count { - let mut col = self.cursor.point.col; - for i in (0..(col.0)).rev() { - if self.tabs[index::Column(i)] { - col = index::Column(i); - break; - } - } - self.cursor.point.col = col; - } - } - - #[inline] - fn move_forward_tabs(&mut self, count: i64) { - trace!("[unimplemented] Moving forward {} tabs", count); - } - - #[inline] - fn save_cursor_position(&mut self) { - trace!("Saving cursor position"); - let cursor = if self.alt { &mut self.cursor_save_alt } else { &mut self.cursor_save }; - - *cursor = self.cursor; - } - - #[inline] - fn restore_cursor_position(&mut self) { - trace!("Restoring cursor position"); - let source = if self.alt { &self.cursor_save_alt } else { &self.cursor_save }; - - self.cursor = *source; - self.cursor.point.line = min(self.cursor.point.line, self.grid.num_lines() - 1); - self.cursor.point.col = min(self.cursor.point.col, self.grid.num_cols() - 1); - } - - #[inline] - fn clear_line(&mut self, mode: ansi::LineClearMode) { - trace!("Clearing line: {:?}", mode); - let mut template = self.cursor.template; - template.flags ^= template.flags; - - let col = self.cursor.point.col; - - match mode { - ansi::LineClearMode::Right => { - let row = &mut self.grid[self.cursor.point.line]; - for cell in &mut row[col..] { - cell.reset(&template); - } - }, - ansi::LineClearMode::Left => { - let row = &mut self.grid[self.cursor.point.line]; - for cell in &mut row[..=col] { - cell.reset(&template); - } - }, - ansi::LineClearMode::All => { - let row = &mut self.grid[self.cursor.point.line]; - for cell in &mut row[..] { - cell.reset(&template); - } - }, - } - } - - /// Set the indexed color value - #[inline] - fn set_color(&mut self, index: usize, color: Rgb) { - trace!("Setting color[{}] = {:?}", index, color); - self.colors[index] = color; - self.color_modified[index] = true; - } - - /// Reset the indexed color to original value - #[inline] - fn reset_color(&mut self, index: usize) { - trace!("Reseting color[{}]", index); - self.colors[index] = self.original_colors[index]; - self.color_modified[index] = false; - } - - /// Set the clipboard - #[inline] - fn set_clipboard(&mut self, string: &str) { - Clipboard::new().and_then(|mut clipboard| clipboard.store_primary(string)).unwrap_or_else( - |err| { - warn!("Error storing selection to clipboard: {}", err); - }, - ); - } - - #[inline] - fn clear_screen(&mut self, mode: ansi::ClearMode) { - trace!("Clearing screen: {:?}", mode); - let mut template = self.cursor.template; - template.flags ^= template.flags; - - // Remove active selections and URL highlights - self.grid.selection = None; - self.grid.url_highlight = None; - - match mode { - ansi::ClearMode::Below => { - for cell in &mut self.grid[self.cursor.point.line][self.cursor.point.col..] { - cell.reset(&template); - } - if self.cursor.point.line < self.grid.num_lines() - 1 { - self.grid - .region_mut((self.cursor.point.line + 1)..) - .each(|cell| cell.reset(&template)); - } - }, - ansi::ClearMode::All => self.grid.region_mut(..).each(|c| c.reset(&template)), - ansi::ClearMode::Above => { - // If clearing more than one line - if self.cursor.point.line > Line(1) { - // Fully clear all lines before the current line - self.grid - .region_mut(..self.cursor.point.line) - .each(|cell| cell.reset(&template)); - } - // Clear up to the current column in the current line - let end = min(self.cursor.point.col + 1, self.grid.num_cols()); - for cell in &mut self.grid[self.cursor.point.line][..end] { - cell.reset(&template); - } - }, - ansi::ClearMode::Saved => self.grid.clear_history(), - } - } - - #[inline] - fn clear_tabs(&mut self, mode: ansi::TabulationClearMode) { - trace!("Clearing tabs: {:?}", mode); - match mode { - ansi::TabulationClearMode::Current => { - let column = self.cursor.point.col; - self.tabs[column] = false; - }, - ansi::TabulationClearMode::All => { - self.tabs.clear_all(); - }, - } - } - - // Reset all important fields in the term struct - #[inline] - fn reset_state(&mut self) { - if self.alt { - self.swap_alt(); - } - self.input_needs_wrap = false; - self.next_title = None; - self.next_mouse_cursor = None; - self.cursor = Default::default(); - self.active_charset = Default::default(); - self.mode = Default::default(); - self.font_size = self.original_font_size; - self.next_is_urgent = None; - self.cursor_save = Default::default(); - self.cursor_save_alt = Default::default(); - self.colors = self.original_colors; - self.color_modified = [false; color::COUNT]; - self.cursor_style = None; - self.grid.reset(&Cell::default()); - self.alt_grid.reset(&Cell::default()); - self.scroll_region = Line(0)..self.grid.num_lines(); - } - - #[inline] - fn reverse_index(&mut self) { - trace!("Reversing index"); - // if cursor is at the top - if self.cursor.point.line == self.scroll_region.start { - self.scroll_down(Line(1)); - } else { - self.cursor.point.line -= min(self.cursor.point.line, Line(1)); - } - } - - /// set a terminal attribute - #[inline] - fn terminal_attribute(&mut self, attr: Attr) { - trace!("Setting attribute: {:?}", attr); - match attr { - Attr::Foreground(color) => self.cursor.template.fg = color, - Attr::Background(color) => self.cursor.template.bg = color, - Attr::Reset => { - self.cursor.template.fg = Color::Named(NamedColor::Foreground); - self.cursor.template.bg = Color::Named(NamedColor::Background); - self.cursor.template.flags = cell::Flags::empty(); - }, - Attr::Reverse => self.cursor.template.flags.insert(cell::Flags::INVERSE), - Attr::CancelReverse => self.cursor.template.flags.remove(cell::Flags::INVERSE), - Attr::Bold => self.cursor.template.flags.insert(cell::Flags::BOLD), - Attr::CancelBold => self.cursor.template.flags.remove(cell::Flags::BOLD), - Attr::Dim => self.cursor.template.flags.insert(cell::Flags::DIM), - Attr::CancelBoldDim => { - self.cursor.template.flags.remove(cell::Flags::BOLD | cell::Flags::DIM) - }, - Attr::Italic => self.cursor.template.flags.insert(cell::Flags::ITALIC), - Attr::CancelItalic => self.cursor.template.flags.remove(cell::Flags::ITALIC), - Attr::Underscore => self.cursor.template.flags.insert(cell::Flags::UNDERLINE), - Attr::CancelUnderline => self.cursor.template.flags.remove(cell::Flags::UNDERLINE), - Attr::Hidden => self.cursor.template.flags.insert(cell::Flags::HIDDEN), - Attr::CancelHidden => self.cursor.template.flags.remove(cell::Flags::HIDDEN), - Attr::Strike => self.cursor.template.flags.insert(cell::Flags::STRIKEOUT), - Attr::CancelStrike => self.cursor.template.flags.remove(cell::Flags::STRIKEOUT), - _ => { - debug!("Term got unhandled attr: {:?}", attr); - }, - } - } - - #[inline] - fn set_mode(&mut self, mode: ansi::Mode) { - trace!("Setting mode: {:?}", mode); - match mode { - ansi::Mode::SwapScreenAndSetRestoreCursor => { - if !self.alt { - self.mode.insert(TermMode::ALT_SCREEN); - self.save_cursor_position(); - self.swap_alt(); - self.save_cursor_position(); - } - }, - ansi::Mode::ShowCursor => self.mode.insert(TermMode::SHOW_CURSOR), - ansi::Mode::CursorKeys => self.mode.insert(TermMode::APP_CURSOR), - ansi::Mode::ReportMouseClicks => { - self.mode.insert(TermMode::MOUSE_REPORT_CLICK); - self.set_mouse_cursor(MouseCursor::Default); - }, - ansi::Mode::ReportCellMouseMotion => { - self.mode.insert(TermMode::MOUSE_DRAG); - self.set_mouse_cursor(MouseCursor::Default); - }, - ansi::Mode::ReportAllMouseMotion => { - self.mode.insert(TermMode::MOUSE_MOTION); - self.set_mouse_cursor(MouseCursor::Default); - }, - ansi::Mode::ReportFocusInOut => self.mode.insert(TermMode::FOCUS_IN_OUT), - ansi::Mode::BracketedPaste => self.mode.insert(TermMode::BRACKETED_PASTE), - ansi::Mode::SgrMouse => self.mode.insert(TermMode::SGR_MOUSE), - ansi::Mode::LineWrap => self.mode.insert(TermMode::LINE_WRAP), - ansi::Mode::LineFeedNewLine => self.mode.insert(TermMode::LINE_FEED_NEW_LINE), - ansi::Mode::Origin => self.mode.insert(TermMode::ORIGIN), - ansi::Mode::DECCOLM => self.deccolm(), - ansi::Mode::Insert => self.mode.insert(TermMode::INSERT), // heh - ansi::Mode::BlinkingCursor => { - trace!("... unimplemented mode"); - }, - } - } - - #[inline] - fn unset_mode(&mut self, mode: ansi::Mode) { - trace!("Unsetting mode: {:?}", mode); - match mode { - ansi::Mode::SwapScreenAndSetRestoreCursor => { - if self.alt { - self.mode.remove(TermMode::ALT_SCREEN); - self.restore_cursor_position(); - self.swap_alt(); - self.restore_cursor_position(); - } - }, - ansi::Mode::ShowCursor => self.mode.remove(TermMode::SHOW_CURSOR), - ansi::Mode::CursorKeys => self.mode.remove(TermMode::APP_CURSOR), - ansi::Mode::ReportMouseClicks => { - self.mode.remove(TermMode::MOUSE_REPORT_CLICK); - self.set_mouse_cursor(MouseCursor::Text); - }, - ansi::Mode::ReportCellMouseMotion => { - self.mode.remove(TermMode::MOUSE_DRAG); - self.set_mouse_cursor(MouseCursor::Text); - }, - ansi::Mode::ReportAllMouseMotion => { - self.mode.remove(TermMode::MOUSE_MOTION); - self.set_mouse_cursor(MouseCursor::Text); - }, - ansi::Mode::ReportFocusInOut => self.mode.remove(TermMode::FOCUS_IN_OUT), - ansi::Mode::BracketedPaste => self.mode.remove(TermMode::BRACKETED_PASTE), - ansi::Mode::SgrMouse => self.mode.remove(TermMode::SGR_MOUSE), - ansi::Mode::LineWrap => self.mode.remove(TermMode::LINE_WRAP), - ansi::Mode::LineFeedNewLine => self.mode.remove(TermMode::LINE_FEED_NEW_LINE), - ansi::Mode::Origin => self.mode.remove(TermMode::ORIGIN), - ansi::Mode::DECCOLM => self.deccolm(), - ansi::Mode::Insert => self.mode.remove(TermMode::INSERT), - ansi::Mode::BlinkingCursor => { - trace!("... unimplemented mode"); - }, - } - } - - #[inline] - fn set_scrolling_region(&mut self, region: Range<Line>) { - trace!("Setting scrolling region: {:?}", region); - self.scroll_region.start = min(region.start, self.grid.num_lines()); - self.scroll_region.end = min(region.end, self.grid.num_lines()); - self.goto(Line(0), Column(0)); - } - - #[inline] - fn set_keypad_application_mode(&mut self) { - trace!("Setting keypad application mode"); - self.mode.insert(TermMode::APP_KEYPAD); - } - - #[inline] - fn unset_keypad_application_mode(&mut self) { - trace!("Unsetting keypad application mode"); - self.mode.remove(TermMode::APP_KEYPAD); - } - - #[inline] - fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) { - trace!("Configuring charset {:?} as {:?}", index, charset); - self.cursor.charsets[index] = charset; - } - - #[inline] - fn set_active_charset(&mut self, index: CharsetIndex) { - trace!("Setting active charset {:?}", index); - self.active_charset = index; - } - - #[inline] - fn set_cursor_style(&mut self, style: Option<CursorStyle>) { - trace!("Setting cursor style {:?}", style); - self.cursor_style = style; - } -} - -struct TabStops { - tabs: Vec<bool>, -} - -impl TabStops { - fn new(num_cols: Column, tabspaces: usize) -> TabStops { - TabStops { - tabs: IndexRange::from(Column(0)..num_cols) - .map(|i| (*i as usize) % tabspaces == 0) - .collect::<Vec<bool>>(), - } - } - - fn clear_all(&mut self) { - unsafe { - ptr::write_bytes(self.tabs.as_mut_ptr(), 0, self.tabs.len()); - } - } -} - -impl Index<Column> for TabStops { - type Output = bool; - - fn index(&self, index: Column) -> &bool { - &self.tabs[index.0] - } -} - -impl IndexMut<Column> for TabStops { - fn index_mut(&mut self, index: Column) -> &mut bool { - self.tabs.index_mut(index.0) - } -} - -#[cfg(test)] -mod tests { - use serde_json; - - use super::{Cell, SizeInfo, Term}; - use crate::term::cell; - - use crate::ansi::{self, CharsetIndex, Handler, StandardCharset}; - use crate::config::Config; - use crate::grid::{Grid, Scroll}; - use crate::index::{Column, Line, Point, Side}; - use crate::input::FONT_SIZE_STEP; - use crate::message_bar::MessageBuffer; - use crate::selection::Selection; - use font::Size; - use std::mem; - - #[test] - fn semantic_selection_works() { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - let mut term = Term::new(&Default::default(), size, MessageBuffer::new()); - let mut grid: Grid<Cell> = Grid::new(Line(3), Column(5), 0, Cell::default()); - for i in 0..5 { - for j in 0..2 { - grid[Line(j)][Column(i)].c = 'a'; - } - } - grid[Line(0)][Column(0)].c = '"'; - grid[Line(0)][Column(3)].c = '"'; - grid[Line(1)][Column(2)].c = '"'; - grid[Line(0)][Column(4)].flags.insert(cell::Flags::WRAPLINE); - - let mut escape_chars = String::from("\""); - - mem::swap(&mut term.grid, &mut grid); - mem::swap(&mut term.semantic_escape_chars, &mut escape_chars); - - { - *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(1) })); - assert_eq!(term.selection_to_string(), Some(String::from("aa"))); - } - - { - *term.selection_mut() = Some(Selection::semantic(Point { line: 2, col: Column(4) })); - assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); - } - - { - *term.selection_mut() = Some(Selection::semantic(Point { line: 1, col: Column(1) })); - assert_eq!(term.selection_to_string(), Some(String::from("aaa"))); - } - } - - #[test] - fn line_selection_works() { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - let mut term = Term::new(&Default::default(), size, MessageBuffer::new()); - let mut grid: Grid<Cell> = Grid::new(Line(1), Column(5), 0, Cell::default()); - for i in 0..5 { - grid[Line(0)][Column(i)].c = 'a'; - } - grid[Line(0)][Column(0)].c = '"'; - grid[Line(0)][Column(3)].c = '"'; - - mem::swap(&mut term.grid, &mut grid); - - *term.selection_mut() = Some(Selection::lines(Point { line: 0, col: Column(3) })); - assert_eq!(term.selection_to_string(), Some(String::from("\"aa\"a\n"))); - } - - #[test] - fn selecting_empty_line() { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - let mut term = Term::new(&Default::default(), size, MessageBuffer::new()); - let mut grid: Grid<Cell> = Grid::new(Line(3), Column(3), 0, Cell::default()); - for l in 0..3 { - if l != 1 { - for c in 0..3 { - grid[Line(l)][Column(c)].c = 'a'; - } - } - } - - mem::swap(&mut term.grid, &mut grid); - - let mut selection = Selection::simple(Point { line: 2, col: Column(0) }, Side::Left); - selection.update(Point { line: 0, col: Column(2) }, Side::Right); - *term.selection_mut() = Some(selection); - assert_eq!(term.selection_to_string(), Some("aaa\n\naaa\n".into())); - } - - /// Check that the grid can be serialized back and forth losslessly - /// - /// This test is in the term module as opposed to the grid since we want to - /// test this property with a T=Cell. - #[test] - fn grid_serde() { - let template = Cell::default(); - - let grid: Grid<Cell> = Grid::new(Line(24), Column(80), 0, template); - let serialized = serde_json::to_string(&grid).expect("ser"); - let deserialized = serde_json::from_str::<Grid<Cell>>(&serialized).expect("de"); - - assert_eq!(deserialized, grid); - } - - #[test] - fn input_line_drawing_character() { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - let mut term = Term::new(&Default::default(), size, MessageBuffer::new()); - let cursor = Point::new(Line(0), Column(0)); - term.configure_charset(CharsetIndex::G0, StandardCharset::SpecialCharacterAndLineDrawing); - term.input('a'); - - assert_eq!(term.grid()[&cursor].c, '▒'); - } - - fn change_font_size_works(font_size: f32) { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size, MessageBuffer::new()); - term.change_font_size(font_size); - - let expected_font_size: Size = config.font().size() + Size::new(font_size); - assert_eq!(term.font_size, expected_font_size); - } - - #[test] - fn increase_font_size_works() { - change_font_size_works(10.0); - } - - #[test] - fn decrease_font_size_works() { - change_font_size_works(-10.0); - } - - #[test] - fn prevent_font_below_threshold_works() { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size, MessageBuffer::new()); - - term.change_font_size(-100.0); - - let expected_font_size: Size = Size::new(FONT_SIZE_STEP); - assert_eq!(term.font_size, expected_font_size); - } - - #[test] - fn reset_font_size_works() { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size, MessageBuffer::new()); - - term.change_font_size(10.0); - term.reset_font_size(); - - let expected_font_size: Size = config.font().size(); - assert_eq!(term.font_size, expected_font_size); - } - - #[test] - fn clear_saved_lines() { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - let config: Config = Default::default(); - let mut term: Term = Term::new(&config, size, MessageBuffer::new()); - - // Add one line of scrollback - term.grid.scroll_up(&(Line(0)..Line(1)), Line(1), &Cell::default()); - - // Clear the history - term.clear_screen(ansi::ClearMode::Saved); - - // Make sure that scrolling does not change the grid - let mut scrolled_grid = term.grid.clone(); - scrolled_grid.scroll_display(Scroll::Top); - assert_eq!(term.grid, scrolled_grid); - } -} - -#[cfg(all(test, feature = "bench"))] -mod benches { - extern crate serde_json as json; - extern crate test; - - use std::fs::File; - use std::io::Read; - use std::mem; - use std::path::Path; - - use crate::config::Config; - use crate::grid::Grid; - use crate::message_bar::MessageBuffer; - - use super::cell::Cell; - use super::{SizeInfo, Term}; - - fn read_string<P>(path: P) -> String - where - P: AsRef<Path>, - { - let mut res = String::new(); - File::open(path.as_ref()).unwrap().read_to_string(&mut res).unwrap(); - - res - } - - /// Benchmark for the renderable cells iterator - /// - /// The renderable cells iterator yields cells that require work to be - /// displayed (that is, not a an empty background cell). This benchmark - /// measures how long it takes to process the whole iterator. - /// - /// When this benchmark was first added, it averaged ~78usec on my macbook - /// pro. The total render time for this grid is anywhere between ~1500 and - /// ~2000usec (measured imprecisely with the visual meter). - #[bench] - fn render_iter(b: &mut test::Bencher) { - // Need some realistic grid state; using one of the ref files. - let serialized_grid = read_string(concat!( - env!("CARGO_MANIFEST_DIR"), - "/tests/ref/vim_large_window_scroll/grid.json" - )); - let serialized_size = read_string(concat!( - env!("CARGO_MANIFEST_DIR"), - "/tests/ref/vim_large_window_scroll/size.json" - )); - - let mut grid: Grid<Cell> = json::from_str(&serialized_grid).unwrap(); - let size: SizeInfo = json::from_str(&serialized_size).unwrap(); - - let config = Config::default(); - - let mut terminal = Term::new(&config, size, MessageBuffer::new()); - mem::swap(&mut terminal.grid, &mut grid); - - let metrics = font::Metrics { - descent: 0., - line_height: 0., - average_advance: 0., - underline_position: 0., - underline_thickness: 0., - strikeout_position: 0., - strikeout_thickness: 0., - }; - - b.iter(|| { - let iter = terminal.renderable_cells(&config, false, metrics); - for cell in iter { - test::black_box(cell); - } - }) - } -} diff --git a/src/tty/mod.rs b/src/tty/mod.rs deleted file mode 100644 index ec175ee6..00000000 --- a/src/tty/mod.rs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! tty related functionality -use mio; -use std::{env, io}; - -use terminfo::Database; - -use crate::config::Config; - -#[cfg(not(windows))] -mod unix; -#[cfg(not(windows))] -pub use self::unix::*; - -#[cfg(windows)] -mod windows; -#[cfg(windows)] -pub use self::windows::*; - -/// This trait defines the behaviour needed to read and/or write to a stream. -/// It defines an abstraction over mio's interface in order to allow either one -/// read/write object or a seperate read and write object. -pub trait EventedReadWrite { - type Reader: io::Read; - type Writer: io::Write; - - fn register( - &mut self, - _: &mio::Poll, - _: &mut dyn Iterator<Item = mio::Token>, - _: mio::Ready, - _: mio::PollOpt, - ) -> io::Result<()>; - fn reregister(&mut self, _: &mio::Poll, _: mio::Ready, _: mio::PollOpt) -> io::Result<()>; - fn deregister(&mut self, _: &mio::Poll) -> io::Result<()>; - - fn reader(&mut self) -> &mut Self::Reader; - fn read_token(&self) -> mio::Token; - fn writer(&mut self) -> &mut Self::Writer; - fn write_token(&self) -> mio::Token; -} - -/// Events concerning TTY child processes -#[derive(PartialEq)] -pub enum ChildEvent { - /// Indicates the child has exited - Exited, -} - -/// A pseudoterminal (or PTY) -/// -/// This is a refinement of EventedReadWrite that also provides a channel through which we can be -/// notified if the PTY child process does something we care about (other than writing to the TTY). -/// In particular, this allows for race-free child exit notification on UNIX (cf. `SIGCHLD`). -pub trait EventedPty: EventedReadWrite { - #[cfg(unix)] - fn child_event_token(&self) -> mio::Token; - - /// Tries to retrieve an event - /// - /// Returns `Some(event)` on success, or `None` if there are no events to retrieve. - #[cfg(unix)] - fn next_child_event(&mut self) -> Option<ChildEvent>; -} - -// Setup environment variables -pub fn setup_env(config: &Config) { - // Default to 'alacritty' terminfo if it is available, otherwise - // default to 'xterm-256color'. May be overridden by user's config - // below. - env::set_var( - "TERM", - if Database::from_name("alacritty").is_ok() { "alacritty" } else { "xterm-256color" }, - ); - - // Advertise 24-bit color support - env::set_var("COLORTERM", "truecolor"); - - // Set env vars from config - for (key, value) in config.env().iter() { - env::set_var(key, value); - } -} diff --git a/src/tty/unix.rs b/src/tty/unix.rs deleted file mode 100644 index 0e3dc2fd..00000000 --- a/src/tty/unix.rs +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//! tty related functionality -//! - -use crate::cli::Options; -use crate::config::{Config, Shell}; -use crate::display::OnResize; -use crate::term::SizeInfo; -use crate::tty::{ChildEvent, EventedPty, EventedReadWrite}; -use mio; - -use libc::{self, c_int, pid_t, winsize, TIOCSCTTY}; -use nix::pty::openpty; -use signal_hook::{self as sighook, iterator::Signals}; - -use mio::unix::EventedFd; -use std::ffi::CStr; -use std::fs::File; -use std::io; -use std::os::unix::{ - io::{AsRawFd, FromRawFd, RawFd}, - process::CommandExt, -}; -use std::process::{Child, Command, Stdio}; -use std::ptr; -use std::sync::atomic::{AtomicUsize, Ordering}; - -/// Process ID of child process -/// -/// Necessary to put this in static storage for `sigchld` to have access -static PID: AtomicUsize = AtomicUsize::new(0); - -pub fn child_pid() -> pid_t { - PID.load(Ordering::Relaxed) as pid_t -} - -/// Get the current value of errno -fn errno() -> c_int { - ::errno::errno().0 -} - -/// Get raw fds for master/slave ends of a new pty -fn make_pty(size: winsize) -> (RawFd, RawFd) { - let mut win_size = size; - win_size.ws_xpixel = 0; - win_size.ws_ypixel = 0; - - let ends = openpty(Some(&win_size), None).expect("openpty failed"); - - (ends.master, ends.slave) -} - -/// Really only needed on BSD, but should be fine elsewhere -fn set_controlling_terminal(fd: c_int) { - let res = unsafe { - // TIOSCTTY changes based on platform and the `ioctl` call is different - // based on architecture (32/64). So a generic cast is used to make sure - // there are no issues. To allow such a generic cast the clippy warning - // is disabled. - #[allow(clippy::cast_lossless)] - libc::ioctl(fd, TIOCSCTTY as _, 0) - }; - - if res < 0 { - die!("ioctl TIOCSCTTY failed: {}", errno()); - } -} - -#[derive(Debug)] -struct Passwd<'a> { - name: &'a str, - passwd: &'a str, - uid: libc::uid_t, - gid: libc::gid_t, - gecos: &'a str, - dir: &'a str, - shell: &'a str, -} - -/// Return a Passwd struct with pointers into the provided buf -/// -/// # Unsafety -/// -/// If `buf` is changed while `Passwd` is alive, bad thing will almost certainly happen. -fn get_pw_entry(buf: &mut [i8; 1024]) -> Passwd<'_> { - // Create zeroed passwd struct - let mut entry: libc::passwd = unsafe { ::std::mem::uninitialized() }; - - let mut res: *mut libc::passwd = ptr::null_mut(); - - // Try and read the pw file. - let uid = unsafe { libc::getuid() }; - let status = unsafe { - libc::getpwuid_r(uid, &mut entry, buf.as_mut_ptr() as *mut _, buf.len(), &mut res) - }; - - if status < 0 { - die!("getpwuid_r failed"); - } - - if res.is_null() { - die!("pw not found"); - } - - // sanity check - assert_eq!(entry.pw_uid, uid); - - // Build a borrowed Passwd struct - Passwd { - name: unsafe { CStr::from_ptr(entry.pw_name).to_str().unwrap() }, - passwd: unsafe { CStr::from_ptr(entry.pw_passwd).to_str().unwrap() }, - uid: entry.pw_uid, - gid: entry.pw_gid, - gecos: unsafe { CStr::from_ptr(entry.pw_gecos).to_str().unwrap() }, - dir: unsafe { CStr::from_ptr(entry.pw_dir).to_str().unwrap() }, - shell: unsafe { CStr::from_ptr(entry.pw_shell).to_str().unwrap() }, - } -} - -pub struct Pty { - child: Child, - pub fd: File, - token: mio::Token, - signals: Signals, - signals_token: mio::Token, -} - -impl Pty { - /// Resize the pty - /// - /// Tells the kernel that the window size changed with the new pixel - /// dimensions and line/column counts. - pub fn resize<T: ToWinsize>(&self, size: &T) { - let win = size.to_winsize(); - - let res = unsafe { libc::ioctl(self.fd.as_raw_fd(), libc::TIOCSWINSZ, &win as *const _) }; - - if res < 0 { - die!("ioctl TIOCSWINSZ failed: {}", errno()); - } - } -} - -/// Create a new tty and return a handle to interact with it. -pub fn new<T: ToWinsize>( - config: &Config, - options: &Options, - size: &T, - window_id: Option<usize>, -) -> Pty { - let win_size = size.to_winsize(); - let mut buf = [0; 1024]; - let pw = get_pw_entry(&mut buf); - - let (master, slave) = make_pty(win_size); - - let default_shell = if cfg!(target_os = "macos") { - let shell_name = pw.shell.rsplit('/').next().unwrap(); - let argv = vec![String::from("-c"), format!("exec -a -{} {}", shell_name, pw.shell)]; - - Shell::new_with_args("/bin/bash", argv) - } else { - Shell::new(pw.shell) - }; - let shell = config.shell().unwrap_or(&default_shell); - - let initial_command = options.command().unwrap_or(shell); - - let mut builder = Command::new(initial_command.program()); - for arg in initial_command.args() { - builder.arg(arg); - } - - // Setup child stdin/stdout/stderr as slave fd of pty - // Ownership of fd is transferred to the Stdio structs and will be closed by them at the end of - // this scope. (It is not an issue that the fd is closed three times since File::drop ignores - // error on libc::close.) - builder.stdin(unsafe { Stdio::from_raw_fd(slave) }); - builder.stderr(unsafe { Stdio::from_raw_fd(slave) }); - builder.stdout(unsafe { Stdio::from_raw_fd(slave) }); - - // Setup shell environment - builder.env("LOGNAME", pw.name); - builder.env("USER", pw.name); - builder.env("SHELL", pw.shell); - builder.env("HOME", pw.dir); - - if let Some(window_id) = window_id { - builder.env("WINDOWID", format!("{}", window_id)); - } - - builder.before_exec(move || { - // Create a new process group - unsafe { - let err = libc::setsid(); - if err == -1 { - die!("Failed to set session id: {}", errno()); - } - } - - set_controlling_terminal(slave); - - // No longer need slave/master fds - unsafe { - libc::close(slave); - libc::close(master); - } - - unsafe { - libc::signal(libc::SIGCHLD, libc::SIG_DFL); - libc::signal(libc::SIGHUP, libc::SIG_DFL); - libc::signal(libc::SIGINT, libc::SIG_DFL); - libc::signal(libc::SIGQUIT, libc::SIG_DFL); - libc::signal(libc::SIGTERM, libc::SIG_DFL); - libc::signal(libc::SIGALRM, libc::SIG_DFL); - } - Ok(()) - }); - - // Handle set working directory option - if let Some(ref dir) = options.working_dir { - builder.current_dir(dir.as_path()); - } - - // Prepare signal handling before spawning child - let signals = Signals::new(&[sighook::SIGCHLD]).expect("error preparing signal handling"); - - match builder.spawn() { - Ok(child) => { - // Remember child PID so other modules can use it - PID.store(child.id() as usize, Ordering::Relaxed); - - unsafe { - // Maybe this should be done outside of this function so nonblocking - // isn't forced upon consumers. Although maybe it should be? - set_nonblocking(master); - } - - let pty = Pty { - child, - fd: unsafe { File::from_raw_fd(master) }, - token: mio::Token::from(0), - signals, - signals_token: mio::Token::from(0), - }; - pty.resize(size); - pty - }, - Err(err) => { - die!("Failed to spawn command: {}", err); - }, - } -} - -impl EventedReadWrite for Pty { - type Reader = File; - type Writer = File; - - #[inline] - fn register( - &mut self, - poll: &mio::Poll, - token: &mut dyn Iterator<Item = mio::Token>, - interest: mio::Ready, - poll_opts: mio::PollOpt, - ) -> io::Result<()> { - self.token = token.next().unwrap(); - poll.register(&EventedFd(&self.fd.as_raw_fd()), self.token, interest, poll_opts)?; - - self.signals_token = token.next().unwrap(); - poll.register( - &self.signals, - self.signals_token, - mio::Ready::readable(), - mio::PollOpt::level(), - ) - } - - #[inline] - fn reregister( - &mut self, - poll: &mio::Poll, - interest: mio::Ready, - poll_opts: mio::PollOpt, - ) -> io::Result<()> { - poll.reregister(&EventedFd(&self.fd.as_raw_fd()), self.token, interest, poll_opts)?; - - poll.reregister( - &self.signals, - self.signals_token, - mio::Ready::readable(), - mio::PollOpt::level(), - ) - } - - #[inline] - fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> { - poll.deregister(&EventedFd(&self.fd.as_raw_fd()))?; - poll.deregister(&self.signals) - } - - #[inline] - fn reader(&mut self) -> &mut File { - &mut self.fd - } - - #[inline] - fn read_token(&self) -> mio::Token { - self.token - } - - #[inline] - fn writer(&mut self) -> &mut File { - &mut self.fd - } - - #[inline] - fn write_token(&self) -> mio::Token { - self.token - } -} - -impl EventedPty for Pty { - #[inline] - fn next_child_event(&mut self) -> Option<ChildEvent> { - self.signals.pending().next().and_then(|signal| { - if signal != sighook::SIGCHLD { - return None; - } - - match self.child.try_wait() { - Err(e) => { - error!("Error checking child process termination: {}", e); - None - }, - Ok(None) => None, - Ok(_) => Some(ChildEvent::Exited), - } - }) - } - - #[inline] - fn child_event_token(&self) -> mio::Token { - self.signals_token - } -} - -pub fn process_should_exit() -> bool { - false -} - -/// Types that can produce a `libc::winsize` -pub trait ToWinsize { - /// Get a `libc::winsize` - fn to_winsize(&self) -> winsize; -} - -impl<'a> ToWinsize for &'a SizeInfo { - fn to_winsize(&self) -> winsize { - winsize { - ws_row: self.lines().0 as libc::c_ushort, - ws_col: self.cols().0 as libc::c_ushort, - ws_xpixel: self.width as libc::c_ushort, - ws_ypixel: self.height as libc::c_ushort, - } - } -} - -impl OnResize for i32 { - fn on_resize(&mut self, size: &SizeInfo) { - let win = size.to_winsize(); - - let res = unsafe { libc::ioctl(*self, libc::TIOCSWINSZ, &win as *const _) }; - - if res < 0 { - die!("ioctl TIOCSWINSZ failed: {}", errno()); - } - } -} - -unsafe fn set_nonblocking(fd: c_int) { - use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK}; - - let res = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); - assert_eq!(res, 0); -} - -#[test] -fn test_get_pw_entry() { - let mut buf: [i8; 1024] = [0; 1024]; - let _pw = get_pw_entry(&mut buf); -} diff --git a/src/tty/windows/conpty.rs b/src/tty/windows/conpty.rs deleted file mode 100644 index f23d78a7..00000000 --- a/src/tty/windows/conpty.rs +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{Pty, HANDLE}; - -use std::i16; -use std::io::Error; -use std::mem; -use std::os::windows::io::IntoRawHandle; -use std::ptr; -use std::sync::Arc; - -use dunce::canonicalize; -use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite}; -use miow; -use widestring::U16CString; -use winapi::shared::basetsd::{PSIZE_T, SIZE_T}; -use winapi::shared::minwindef::{BYTE, DWORD}; -use winapi::shared::ntdef::{HANDLE, HRESULT, LPWSTR}; -use winapi::shared::winerror::S_OK; -use winapi::um::libloaderapi::{GetModuleHandleA, GetProcAddress}; -use winapi::um::processthreadsapi::{ - CreateProcessW, InitializeProcThreadAttributeList, UpdateProcThreadAttribute, - PROCESS_INFORMATION, STARTUPINFOW, -}; -use winapi::um::winbase::{EXTENDED_STARTUPINFO_PRESENT, STARTF_USESTDHANDLES, STARTUPINFOEXW}; -use winapi::um::wincontypes::{COORD, HPCON}; - -use crate::cli::Options; -use crate::config::{Config, Shell}; -use crate::display::OnResize; -use crate::term::SizeInfo; - -/// Dynamically-loaded Pseudoconsole API from kernel32.dll -/// -/// The field names are deliberately PascalCase as this matches -/// the defined symbols in kernel32 and also is the convention -/// that the `winapi` crate follows. -#[allow(non_snake_case)] -struct ConptyApi { - CreatePseudoConsole: - unsafe extern "system" fn(COORD, HANDLE, HANDLE, DWORD, *mut HPCON) -> HRESULT, - ResizePseudoConsole: unsafe extern "system" fn(HPCON, COORD) -> HRESULT, - ClosePseudoConsole: unsafe extern "system" fn(HPCON), -} - -impl ConptyApi { - /// Load the API or None if it cannot be found. - pub fn new() -> Option<Self> { - // Unsafe because windows API calls - unsafe { - let hmodule = GetModuleHandleA("kernel32\0".as_ptr() as _); - assert!(!hmodule.is_null()); - - let cpc = GetProcAddress(hmodule, "CreatePseudoConsole\0".as_ptr() as _); - let rpc = GetProcAddress(hmodule, "ResizePseudoConsole\0".as_ptr() as _); - let clpc = GetProcAddress(hmodule, "ClosePseudoConsole\0".as_ptr() as _); - - if cpc.is_null() || rpc.is_null() || clpc.is_null() { - None - } else { - Some(Self { - CreatePseudoConsole: mem::transmute(cpc), - ResizePseudoConsole: mem::transmute(rpc), - ClosePseudoConsole: mem::transmute(clpc), - }) - } - } - } -} - -/// RAII Pseudoconsole -pub struct Conpty { - pub handle: HPCON, - api: ConptyApi, -} - -/// Handle can be cloned freely and moved between threads. -pub type ConptyHandle = Arc<Conpty>; - -impl Drop for Conpty { - fn drop(&mut self) { - unsafe { (self.api.ClosePseudoConsole)(self.handle) } - } -} - -// The Conpty API can be accessed from multiple threads. -unsafe impl Send for Conpty {} -unsafe impl Sync for Conpty {} - -pub fn new<'a>( - config: &Config, - options: &Options, - size: &SizeInfo, - _window_id: Option<usize>, -) -> Option<Pty<'a>> { - if !config.enable_experimental_conpty_backend() { - return None; - } - - let api = ConptyApi::new()?; - - let mut pty_handle = 0 as HPCON; - - // Passing 0 as the size parameter allows the "system default" buffer - // size to be used. There may be small performance and memory advantages - // to be gained by tuning this in the future, but it's likely a reasonable - // start point. - let (conout, conout_pty_handle) = miow::pipe::anonymous(0).unwrap(); - let (conin_pty_handle, conin) = miow::pipe::anonymous(0).unwrap(); - - let coord = - coord_from_sizeinfo(size).expect("Overflow when creating initial size on pseudoconsole"); - - // Create the Pseudo Console, using the pipes - let result = unsafe { - (api.CreatePseudoConsole)( - coord, - conin_pty_handle.into_raw_handle(), - conout_pty_handle.into_raw_handle(), - 0, - &mut pty_handle as *mut HPCON, - ) - }; - - assert!(result == S_OK); - - let mut success; - - // Prepare child process startup info - - let mut size: SIZE_T = 0; - - let mut startup_info_ex: STARTUPINFOEXW = Default::default(); - - let title = options.title.as_ref().map(String::as_str).unwrap_or("Alacritty"); - let title = U16CString::from_str(title).unwrap(); - startup_info_ex.StartupInfo.lpTitle = title.as_ptr() as LPWSTR; - - startup_info_ex.StartupInfo.cb = mem::size_of::<STARTUPINFOEXW>() as u32; - - // Setting this flag but leaving all the handles as default (null) ensures the - // pty process does not inherit any handles from this Alacritty process. - startup_info_ex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; - - // Create the appropriately sized thread attribute list. - unsafe { - let failure = - InitializeProcThreadAttributeList(ptr::null_mut(), 1, 0, &mut size as PSIZE_T) > 0; - - // This call was expected to return false. - if failure { - panic_shell_spawn(); - } - } - - let mut attr_list: Box<[BYTE]> = vec![0; size].into_boxed_slice(); - - // Set startup info's attribute list & initialize it - // - // Lint failure is spurious; it's because winapi's definition of PROC_THREAD_ATTRIBUTE_LIST - // implies it is one pointer in size (32 or 64 bits) but really this is just a dummy value. - // Casting a *mut u8 (pointer to 8 bit type) might therefore not be aligned correctly in - // the compiler's eyes. - #[allow(clippy::cast_ptr_alignment)] - { - startup_info_ex.lpAttributeList = attr_list.as_mut_ptr() as _; - } - - unsafe { - success = InitializeProcThreadAttributeList( - startup_info_ex.lpAttributeList, - 1, - 0, - &mut size as PSIZE_T, - ) > 0; - - if !success { - panic_shell_spawn(); - } - } - - // Set thread attribute list's Pseudo Console to the specified ConPTY - unsafe { - success = UpdateProcThreadAttribute( - startup_info_ex.lpAttributeList, - 0, - 22 | 0x0002_0000, // PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE - pty_handle, - mem::size_of::<HPCON>(), - ptr::null_mut(), - ptr::null_mut(), - ) > 0; - - if !success { - panic_shell_spawn(); - } - } - - // Get process commandline - let default_shell = &Shell::new("powershell"); - let shell = config.shell().unwrap_or(default_shell); - let initial_command = options.command().unwrap_or(shell); - let mut cmdline = initial_command.args().to_vec(); - cmdline.insert(0, initial_command.program().into()); - - // Warning, here be borrow hell - let cwd = options.working_dir.as_ref().map(|dir| canonicalize(dir).unwrap()); - let cwd = cwd.as_ref().map(|dir| dir.to_str().unwrap()); - - // Create the client application, using startup info containing ConPTY info - let cmdline = U16CString::from_str(&cmdline.join(" ")).unwrap(); - let cwd = cwd.map(|s| U16CString::from_str(&s).unwrap()); - - let mut proc_info: PROCESS_INFORMATION = Default::default(); - unsafe { - success = CreateProcessW( - ptr::null(), - cmdline.as_ptr() as LPWSTR, - ptr::null_mut(), - ptr::null_mut(), - false as i32, - EXTENDED_STARTUPINFO_PRESENT, - ptr::null_mut(), - cwd.as_ref().map_or_else(ptr::null, |s| s.as_ptr()), - &mut startup_info_ex.StartupInfo as *mut STARTUPINFOW, - &mut proc_info as *mut PROCESS_INFORMATION, - ) > 0; - - if !success { - panic_shell_spawn(); - } - } - - // Store handle to console - unsafe { - HANDLE = proc_info.hProcess; - } - - let conin = EventedAnonWrite::new(conin); - let conout = EventedAnonRead::new(conout); - - let agent = Conpty { handle: pty_handle, api }; - - Some(Pty { - handle: super::PtyHandle::Conpty(ConptyHandle::new(agent)), - conout: super::EventedReadablePipe::Anonymous(conout), - conin: super::EventedWritablePipe::Anonymous(conin), - read_token: 0.into(), - write_token: 0.into(), - }) -} - -// Panic with the last os error as message -fn panic_shell_spawn() { - panic!("Unable to spawn shell: {}", Error::last_os_error()); -} - -impl OnResize for ConptyHandle { - fn on_resize(&mut self, sizeinfo: &SizeInfo) { - if let Some(coord) = coord_from_sizeinfo(sizeinfo) { - let result = unsafe { (self.api.ResizePseudoConsole)(self.handle, coord) }; - assert!(result == S_OK); - } - } -} - -/// Helper to build a COORD from a SizeInfo, returing None in overflow cases. -fn coord_from_sizeinfo(sizeinfo: &SizeInfo) -> Option<COORD> { - let cols = sizeinfo.cols().0; - let lines = sizeinfo.lines().0; - - if cols <= i16::MAX as usize && lines <= i16::MAX as usize { - Some(COORD { X: sizeinfo.cols().0 as i16, Y: sizeinfo.lines().0 as i16 }) - } else { - None - } -} diff --git a/src/tty/windows/mod.rs b/src/tty/windows/mod.rs deleted file mode 100644 index c87c5257..00000000 --- a/src/tty/windows/mod.rs +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::io::{self, Read, Write}; -use std::os::raw::c_void; -use std::sync::atomic::{AtomicBool, Ordering}; - -use mio::{self, Evented, Poll, PollOpt, Ready, Token}; -use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite}; -use mio_named_pipes::NamedPipe; - -use winapi::shared::winerror::WAIT_TIMEOUT; -use winapi::um::synchapi::WaitForSingleObject; -use winapi::um::winbase::WAIT_OBJECT_0; - -use crate::cli::Options; -use crate::config::Config; -use crate::display::OnResize; -use crate::term::SizeInfo; -use crate::tty::{EventedPty, EventedReadWrite}; - -mod conpty; -mod winpty; - -/// Handle to the winpty agent or conpty process. Required so we know when it closes. -static mut HANDLE: *mut c_void = 0usize as *mut c_void; -static IS_CONPTY: AtomicBool = AtomicBool::new(false); - -pub fn process_should_exit() -> bool { - unsafe { - match WaitForSingleObject(HANDLE, 0) { - // Process has exited - WAIT_OBJECT_0 => { - info!("wait_object_0"); - true - }, - // Reached timeout of 0, process has not exited - WAIT_TIMEOUT => false, - // Error checking process, winpty gave us a bad agent handle? - _ => { - info!("Bad exit: {}", ::std::io::Error::last_os_error()); - true - }, - } - } -} - -pub fn is_conpty() -> bool { - IS_CONPTY.load(Ordering::Relaxed) -} - -#[derive(Clone)] -pub enum PtyHandle<'a> { - Winpty(winpty::WinptyHandle<'a>), - Conpty(conpty::ConptyHandle), -} - -pub struct Pty<'a> { - handle: PtyHandle<'a>, - // TODO: It's on the roadmap for the Conpty API to support Overlapped I/O. - // See https://github.com/Microsoft/console/issues/262 - // When support for that lands then it should be possible to use - // NamedPipe for the conout and conin handles - conout: EventedReadablePipe, - conin: EventedWritablePipe, - read_token: mio::Token, - write_token: mio::Token, -} - -impl<'a> Pty<'a> { - pub fn resize_handle(&self) -> impl OnResize + 'a { - self.handle.clone() - } -} - -pub fn new<'a>( - config: &Config, - options: &Options, - size: &SizeInfo, - window_id: Option<usize>, -) -> Pty<'a> { - if let Some(pty) = conpty::new(config, options, size, window_id) { - info!("Using Conpty agent"); - IS_CONPTY.store(true, Ordering::Relaxed); - pty - } else { - info!("Using Winpty agent"); - winpty::new(config, options, size, window_id) - } -} - -// TODO: The ConPTY API curently must use synchronous pipes as the input -// and output handles. This has led to the need to support two different -// types of pipe. -// -// When https://github.com/Microsoft/console/issues/262 lands then the -// Anonymous variant of this enum can be removed from the codebase and -// everything can just use NamedPipe. -pub enum EventedReadablePipe { - Anonymous(EventedAnonRead), - Named(NamedPipe), -} - -pub enum EventedWritablePipe { - Anonymous(EventedAnonWrite), - Named(NamedPipe), -} - -impl Evented for EventedReadablePipe { - fn register( - &self, - poll: &Poll, - token: Token, - interest: Ready, - opts: PollOpt, - ) -> io::Result<()> { - match self { - EventedReadablePipe::Anonymous(p) => p.register(poll, token, interest, opts), - EventedReadablePipe::Named(p) => p.register(poll, token, interest, opts), - } - } - - fn reregister( - &self, - poll: &Poll, - token: Token, - interest: Ready, - opts: PollOpt, - ) -> io::Result<()> { - match self { - EventedReadablePipe::Anonymous(p) => p.reregister(poll, token, interest, opts), - EventedReadablePipe::Named(p) => p.reregister(poll, token, interest, opts), - } - } - - fn deregister(&self, poll: &Poll) -> io::Result<()> { - match self { - EventedReadablePipe::Anonymous(p) => p.deregister(poll), - EventedReadablePipe::Named(p) => p.deregister(poll), - } - } -} - -impl Read for EventedReadablePipe { - fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { - match self { - EventedReadablePipe::Anonymous(p) => p.read(buf), - EventedReadablePipe::Named(p) => p.read(buf), - } - } -} - -impl Evented for EventedWritablePipe { - fn register( - &self, - poll: &Poll, - token: Token, - interest: Ready, - opts: PollOpt, - ) -> io::Result<()> { - match self { - EventedWritablePipe::Anonymous(p) => p.register(poll, token, interest, opts), - EventedWritablePipe::Named(p) => p.register(poll, token, interest, opts), - } - } - - fn reregister( - &self, - poll: &Poll, - token: Token, - interest: Ready, - opts: PollOpt, - ) -> io::Result<()> { - match self { - EventedWritablePipe::Anonymous(p) => p.reregister(poll, token, interest, opts), - EventedWritablePipe::Named(p) => p.reregister(poll, token, interest, opts), - } - } - - fn deregister(&self, poll: &Poll) -> io::Result<()> { - match self { - EventedWritablePipe::Anonymous(p) => p.deregister(poll), - EventedWritablePipe::Named(p) => p.deregister(poll), - } - } -} - -impl Write for EventedWritablePipe { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - match self { - EventedWritablePipe::Anonymous(p) => p.write(buf), - EventedWritablePipe::Named(p) => p.write(buf), - } - } - - fn flush(&mut self) -> io::Result<()> { - match self { - EventedWritablePipe::Anonymous(p) => p.flush(), - EventedWritablePipe::Named(p) => p.flush(), - } - } -} - -impl<'a> OnResize for PtyHandle<'a> { - fn on_resize(&mut self, sizeinfo: &SizeInfo) { - match self { - PtyHandle::Winpty(w) => w.resize(sizeinfo), - PtyHandle::Conpty(c) => { - let mut handle = c.clone(); - handle.on_resize(sizeinfo) - }, - } - } -} - -impl<'a> EventedReadWrite for Pty<'a> { - type Reader = EventedReadablePipe; - type Writer = EventedWritablePipe; - - #[inline] - fn register( - &mut self, - poll: &mio::Poll, - token: &mut dyn Iterator<Item = mio::Token>, - interest: mio::Ready, - poll_opts: mio::PollOpt, - ) -> io::Result<()> { - self.read_token = token.next().unwrap(); - self.write_token = token.next().unwrap(); - - if interest.is_readable() { - poll.register(&self.conout, self.read_token, mio::Ready::readable(), poll_opts)? - } else { - poll.register(&self.conout, self.read_token, mio::Ready::empty(), poll_opts)? - } - if interest.is_writable() { - poll.register(&self.conin, self.write_token, mio::Ready::writable(), poll_opts)? - } else { - poll.register(&self.conin, self.write_token, mio::Ready::empty(), poll_opts)? - } - Ok(()) - } - - #[inline] - fn reregister( - &mut self, - poll: &mio::Poll, - interest: mio::Ready, - poll_opts: mio::PollOpt, - ) -> io::Result<()> { - if interest.is_readable() { - poll.reregister(&self.conout, self.read_token, mio::Ready::readable(), poll_opts)?; - } else { - poll.reregister(&self.conout, self.read_token, mio::Ready::empty(), poll_opts)?; - } - if interest.is_writable() { - poll.reregister(&self.conin, self.write_token, mio::Ready::writable(), poll_opts)?; - } else { - poll.reregister(&self.conin, self.write_token, mio::Ready::empty(), poll_opts)?; - } - Ok(()) - } - - #[inline] - fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> { - poll.deregister(&self.conout)?; - poll.deregister(&self.conin)?; - Ok(()) - } - - #[inline] - fn reader(&mut self) -> &mut Self::Reader { - &mut self.conout - } - - #[inline] - fn read_token(&self) -> mio::Token { - self.read_token - } - - #[inline] - fn writer(&mut self) -> &mut Self::Writer { - &mut self.conin - } - - #[inline] - fn write_token(&self) -> mio::Token { - self.write_token - } -} - -impl<'a> EventedPty for Pty<'a> {} diff --git a/src/tty/windows/winpty.rs b/src/tty/windows/winpty.rs deleted file mode 100644 index 10bd9d01..00000000 --- a/src/tty/windows/winpty.rs +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{Pty, HANDLE}; - -use std::fs::OpenOptions; -use std::io; -use std::os::windows::fs::OpenOptionsExt; -use std::os::windows::io::{FromRawHandle, IntoRawHandle}; -use std::sync::Arc; -use std::u16; - -use dunce::canonicalize; -use mio_named_pipes::NamedPipe; -use winapi::um::winbase::FILE_FLAG_OVERLAPPED; -use winpty::Config as WinptyConfig; -use winpty::{ConfigFlags, MouseMode, SpawnConfig, SpawnFlags, Winpty}; - -use crate::cli::Options; -use crate::config::{Config, Shell}; -use crate::display::OnResize; -use crate::term::SizeInfo; - -// We store a raw pointer because we need mutable access to call -// on_resize from a separate thread. Winpty internally uses a mutex -// so this is safe, despite outwards appearance. -pub struct Agent<'a> { - winpty: *mut Winpty<'a>, -} - -/// Handle can be cloned freely and moved between threads. -pub type WinptyHandle<'a> = Arc<Agent<'a>>; - -// Because Winpty has a mutex, we can do this. -unsafe impl<'a> Send for Agent<'a> {} -unsafe impl<'a> Sync for Agent<'a> {} - -impl<'a> Agent<'a> { - pub fn new(winpty: Winpty<'a>) -> Self { - Self { winpty: Box::into_raw(Box::new(winpty)) } - } - - /// Get immutable access to Winpty. - pub fn winpty(&self) -> &Winpty<'a> { - unsafe { &*self.winpty } - } - - pub fn resize(&self, size: &SizeInfo) { - // This is safe since Winpty uses a mutex internally. - unsafe { - (&mut *self.winpty).on_resize(size); - } - } -} - -impl<'a> Drop for Agent<'a> { - fn drop(&mut self) { - unsafe { - Box::from_raw(self.winpty); - } - } -} - -/// How long the winpty agent should wait for any RPC request -/// This is a placeholder value until we see how often long responses happen -const AGENT_TIMEOUT: u32 = 10000; - -pub fn new<'a>( - config: &Config, - options: &Options, - size: &SizeInfo, - _window_id: Option<usize>, -) -> Pty<'a> { - // Create config - let mut wconfig = WinptyConfig::new(ConfigFlags::empty()).unwrap(); - - wconfig.set_initial_size(size.cols().0 as i32, size.lines().0 as i32); - wconfig.set_mouse_mode(&MouseMode::Auto); - wconfig.set_agent_timeout(AGENT_TIMEOUT); - - // Start agent - let mut winpty = Winpty::open(&wconfig).unwrap(); - let (conin, conout) = (winpty.conin_name(), winpty.conout_name()); - - // Get process commandline - let default_shell = &Shell::new("powershell"); - let shell = config.shell().unwrap_or(default_shell); - let initial_command = options.command().unwrap_or(shell); - let mut cmdline = initial_command.args().to_vec(); - cmdline.insert(0, initial_command.program().into()); - - // Warning, here be borrow hell - let cwd = options.working_dir.as_ref().map(|dir| canonicalize(dir).unwrap()); - let cwd = cwd.as_ref().map(|dir| dir.to_str().unwrap()); - - // Spawn process - let spawnconfig = SpawnConfig::new( - SpawnFlags::AUTO_SHUTDOWN | SpawnFlags::EXIT_AFTER_SHUTDOWN, - None, // appname - Some(&cmdline.join(" ")), - cwd, - None, // Env - ) - .unwrap(); - - let default_opts = &mut OpenOptions::new(); - default_opts.share_mode(0).custom_flags(FILE_FLAG_OVERLAPPED); - - let (conout_pipe, conin_pipe); - unsafe { - conout_pipe = NamedPipe::from_raw_handle( - default_opts.clone().read(true).open(conout).unwrap().into_raw_handle(), - ); - conin_pipe = NamedPipe::from_raw_handle( - default_opts.clone().write(true).open(conin).unwrap().into_raw_handle(), - ); - }; - - if let Some(err) = conout_pipe.connect().err() { - if err.kind() != io::ErrorKind::WouldBlock { - panic!(err); - } - } - assert!(conout_pipe.take_error().unwrap().is_none()); - - if let Some(err) = conin_pipe.connect().err() { - if err.kind() != io::ErrorKind::WouldBlock { - panic!(err); - } - } - assert!(conin_pipe.take_error().unwrap().is_none()); - - winpty.spawn(&spawnconfig).unwrap(); - - unsafe { - HANDLE = winpty.raw_handle(); - } - - let agent = Agent::new(winpty); - - Pty { - handle: super::PtyHandle::Winpty(WinptyHandle::new(agent)), - conout: super::EventedReadablePipe::Named(conout_pipe), - conin: super::EventedWritablePipe::Named(conin_pipe), - read_token: 0.into(), - write_token: 0.into(), - } -} - -impl<'a> OnResize for Winpty<'a> { - fn on_resize(&mut self, sizeinfo: &SizeInfo) { - let (cols, lines) = (sizeinfo.cols().0, sizeinfo.lines().0); - if cols > 0 && cols <= u16::MAX as usize && lines > 0 && lines <= u16::MAX as usize { - self.set_size(cols as u16, lines as u16) - .unwrap_or_else(|_| info!("Unable to set winpty size, did it die?")); - } - } -} diff --git a/src/url.rs b/src/url.rs deleted file mode 100644 index d3caf9fc..00000000 --- a/src/url.rs +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use unicode_width::UnicodeWidthChar; -use url; - -use crate::term::cell::{Cell, Flags}; - -// See https://tools.ietf.org/html/rfc3987#page-13 -const URL_SEPARATOR_CHARS: [char; 10] = ['<', '>', '"', ' ', '{', '}', '|', '\\', '^', '`']; -const URL_DENY_END_CHARS: [char; 8] = ['.', ',', ';', ':', '?', '!', '/', '(']; -const URL_SCHEMES: [&str; 8] = ["http", "https", "mailto", "news", "file", "git", "ssh", "ftp"]; - -/// URL text and origin of the original click position. -#[derive(Debug, PartialEq)] -pub struct Url { - pub text: String, - pub origin: usize, -} - -/// Parser for streaming inside-out detection of URLs. -pub struct UrlParser { - state: String, - origin: usize, -} - -impl UrlParser { - pub fn new() -> Self { - UrlParser { state: String::new(), origin: 0 } - } - - /// Advance the parser one character to the left. - pub fn advance_left(&mut self, cell: &Cell) -> bool { - if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { - self.origin += 1; - return false; - } - - if self.advance(cell.c, 0) { - true - } else { - self.origin += 1; - false - } - } - - /// Advance the parser one character to the right. - pub fn advance_right(&mut self, cell: &Cell) -> bool { - if cell.flags.contains(Flags::WIDE_CHAR_SPACER) { - return false; - } - - self.advance(cell.c, self.state.len()) - } - - /// Returns the URL if the parser has found any. - pub fn url(mut self) -> Option<Url> { - // Remove non-alphabetical characters before the scheme - // https://tools.ietf.org/html/rfc3986#section-3.1 - if let Some(index) = self.state.find("://") { - let iter = - self.state.char_indices().rev().skip_while(|(byte_index, _)| *byte_index >= index); - for (byte_index, c) in iter { - match c { - 'a'...'z' | 'A'...'Z' => (), - _ => { - self.origin = - self.origin.saturating_sub(byte_index + c.width().unwrap_or(1)); - self.state = self.state.split_off(byte_index + c.len_utf8()); - break; - }, - } - } - } - - // Remove non-matching parenthesis and brackets - let mut open_parens_count: isize = 0; - let mut open_bracks_count: isize = 0; - for (i, c) in self.state.char_indices() { - match c { - '(' => open_parens_count += 1, - ')' if open_parens_count > 0 => open_parens_count -= 1, - '[' => open_bracks_count += 1, - ']' if open_bracks_count > 0 => open_bracks_count -= 1, - ')' | ']' => { - self.state.truncate(i); - break; - }, - _ => (), - } - } - - // Track number of quotes - let mut num_quotes = self.state.chars().filter(|&c| c == '\'').count(); - - // Remove all characters which aren't allowed at the end of a URL - while !self.state.is_empty() - && (URL_DENY_END_CHARS.contains(&self.state.chars().last().unwrap()) - || (num_quotes % 2 != 0 && self.state.ends_with('\'')) - || self.state.ends_with("''") - || self.state.ends_with("()")) - { - if self.state.pop().unwrap() == '\'' { - num_quotes -= 1; - } - } - - // Check if string is valid url - match url::Url::parse(&self.state) { - Ok(url) => { - if URL_SCHEMES.contains(&url.scheme()) && self.origin > 0 { - Some(Url { origin: self.origin - 1, text: self.state }) - } else { - None - } - }, - Err(_) => None, - } - } - - fn advance(&mut self, c: char, pos: usize) -> bool { - if URL_SEPARATOR_CHARS.contains(&c) - || (c >= '\u{00}' && c <= '\u{1F}') - || (c >= '\u{7F}' && c <= '\u{9F}') - { - true - } else { - self.state.insert(pos, c); - false - } - } -} - -#[cfg(test)] -mod tests { - use std::mem; - - use unicode_width::UnicodeWidthChar; - - use crate::grid::Grid; - use crate::index::{Column, Line, Point}; - use crate::message_bar::MessageBuffer; - use crate::term::cell::{Cell, Flags}; - use crate::term::{Search, SizeInfo, Term}; - - fn url_create_term(input: &str) -> Term { - let size = SizeInfo { - width: 21.0, - height: 51.0, - cell_width: 3.0, - cell_height: 3.0, - padding_x: 0.0, - padding_y: 0.0, - dpr: 1.0, - }; - - let width = input.chars().map(|c| if c.width() == Some(2) { 2 } else { 1 }).sum(); - let mut term = Term::new(&Default::default(), size, MessageBuffer::new()); - let mut grid: Grid<Cell> = Grid::new(Line(1), Column(width), 0, Cell::default()); - - let mut i = 0; - for c in input.chars() { - grid[Line(0)][Column(i)].c = c; - - if c.width() == Some(2) { - grid[Line(0)][Column(i)].flags.insert(Flags::WIDE_CHAR); - grid[Line(0)][Column(i + 1)].flags.insert(Flags::WIDE_CHAR_SPACER); - grid[Line(0)][Column(i + 1)].c = ' '; - i += 1; - } - - i += 1; - } - - mem::swap(term.grid_mut(), &mut grid); - - term - } - - fn url_test(input: &str, expected: &str) { - let term = url_create_term(input); - let url = term.url_search(Point::new(0, Column(15))); - assert_eq!(url.map(|u| u.text), Some(expected.into())); - } - - #[test] - fn url_skip_invalid() { - let term = url_create_term("no url here"); - let url = term.url_search(Point::new(0, Column(4))); - assert_eq!(url, None); - - let term = url_create_term(" https://example.org"); - let url = term.url_search(Point::new(0, Column(0))); - assert_eq!(url, None); - } - - #[test] - fn url_origin() { - let term = url_create_term(" test https://example.org "); - let url = term.url_search(Point::new(0, Column(10))); - assert_eq!(url.map(|u| u.origin), Some(4)); - - let term = url_create_term("https://example.org"); - let url = term.url_search(Point::new(0, Column(0))); - assert_eq!(url.map(|u| u.origin), Some(0)); - - let term = url_create_term("https://全.org"); - let url = term.url_search(Point::new(0, Column(10))); - assert_eq!(url.map(|u| u.origin), Some(10)); - - let term = url_create_term("https://全.org"); - let url = term.url_search(Point::new(0, Column(8))); - assert_eq!(url.map(|u| u.origin), Some(8)); - - let term = url_create_term("https://全.org"); - let url = term.url_search(Point::new(0, Column(9))); - assert_eq!(url.map(|u| u.origin), Some(9)); - - let term = url_create_term("test@https://example.org"); - let url = term.url_search(Point::new(0, Column(9))); - assert_eq!(url.map(|u| u.origin), Some(4)); - - let term = url_create_term("test全https://example.org"); - let url = term.url_search(Point::new(0, Column(9))); - assert_eq!(url.map(|u| u.origin), Some(3)); - } - - #[test] - fn url_matching_chars() { - url_test("(https://example.org/test(ing))", "https://example.org/test(ing)"); - url_test("https://example.org/test(ing)", "https://example.org/test(ing)"); - url_test("((https://example.org))", "https://example.org"); - url_test(")https://example.org(", "https://example.org"); - url_test("https://example.org)", "https://example.org"); - url_test("https://example.org(", "https://example.org"); - url_test("(https://one.org/)(https://two.org/)", "https://one.org"); - - url_test("https://[2001:db8:a0b:12f0::1]:80", "https://[2001:db8:a0b:12f0::1]:80"); - url_test("([(https://example.org/test(ing))])", "https://example.org/test(ing)"); - url_test("https://example.org/]()", "https://example.org"); - url_test("[https://example.org]", "https://example.org"); - - url_test("'https://example.org/test'ing'''", "https://example.org/test'ing'"); - url_test("https://example.org/test'ing'", "https://example.org/test'ing'"); - url_test("'https://example.org'", "https://example.org"); - url_test("'https://example.org", "https://example.org"); - url_test("https://example.org'", "https://example.org"); - - url_test("(https://example.org/test全)", "https://example.org/test全"); - } - - #[test] - fn url_detect_end() { - url_test("https://example.org/test\u{00}ing", "https://example.org/test"); - url_test("https://example.org/test\u{1F}ing", "https://example.org/test"); - url_test("https://example.org/test\u{7F}ing", "https://example.org/test"); - url_test("https://example.org/test\u{9F}ing", "https://example.org/test"); - url_test("https://example.org/test\ting", "https://example.org/test"); - url_test("https://example.org/test ing", "https://example.org/test"); - } - - #[test] - fn url_remove_end_chars() { - url_test("https://example.org/test?ing", "https://example.org/test?ing"); - url_test("https://example.org.,;:)'!/?", "https://example.org"); - url_test("https://example.org'.", "https://example.org"); - } - - #[test] - fn url_remove_start_chars() { - url_test("complicated:https://example.org", "https://example.org"); - url_test("test.https://example.org", "https://example.org"); - url_test(",https://example.org", "https://example.org"); - url_test("\u{2502}https://example.org", "https://example.org"); - } - - #[test] - fn url_unicode() { - url_test("https://xn--example-2b07f.org", "https://xn--example-2b07f.org"); - url_test("https://example.org/\u{2008A}", "https://example.org/\u{2008A}"); - url_test("https://example.org/\u{f17c}", "https://example.org/\u{f17c}"); - url_test("https://üñîçøðé.com/ä", "https://üñîçøðé.com/ä"); - } - - #[test] - fn url_schemes() { - url_test("mailto://example.org", "mailto://example.org"); - url_test("https://example.org", "https://example.org"); - url_test("http://example.org", "http://example.org"); - url_test("news://example.org", "news://example.org"); - url_test("file://example.org", "file://example.org"); - url_test("git://example.org", "git://example.org"); - url_test("ssh://example.org", "ssh://example.org"); - url_test("ftp://example.org", "ftp://example.org"); - } -} diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index b8703012..00000000 --- a/src/util.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::ffi::OsStr; -use std::process::{Command, Stdio}; -use std::{cmp, io}; - -#[cfg(not(windows))] -use std::os::unix::process::CommandExt; - -#[cfg(windows)] -use std::os::windows::process::CommandExt; -#[cfg(windows)] -use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW}; - -/// Threading utilities -pub mod thread { - /// Like `thread::spawn`, but with a `name` argument - pub fn spawn_named<F, T, S>(name: S, f: F) -> ::std::thread::JoinHandle<T> - where - F: FnOnce() -> T, - F: Send + 'static, - T: Send + 'static, - S: Into<String>, - { - ::std::thread::Builder::new().name(name.into()).spawn(f).expect("thread spawn works") - } - - pub use std::thread::*; -} - -pub fn limit<T: Ord>(value: T, min: T, max: T) -> T { - cmp::min(cmp::max(value, min), max) -} - -/// Utilities for writing to the -pub mod fmt { - use std::fmt; - - macro_rules! define_colors { - ($($(#[$attrs:meta])* pub struct $s:ident => $color:expr;)*) => { - $( - $(#[$attrs])* - pub struct $s<T>(pub T); - - impl<T: fmt::Display> fmt::Display for $s<T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, concat!("\x1b[", $color, "m{}\x1b[0m"), self.0) - } - } - - impl<T: fmt::Debug> fmt::Debug for $s<T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, concat!("\x1b[", $color, "m{:?}\x1b[0m"), self.0) - } - } - )* - } - } - - define_colors! { - /// Write a `Display` or `Debug` escaped with Red - pub struct Red => "31"; - - /// Write a `Display` or `Debug` escaped with Green - pub struct Green => "32"; - - /// Write a `Display` or `Debug` escaped with Yellow - pub struct Yellow => "33"; - } -} - -#[cfg(not(windows))] -pub fn start_daemon<I, S>(program: &str, args: I) -> io::Result<()> -where - I: IntoIterator<Item = S>, - S: AsRef<OsStr>, -{ - Command::new(program) - .args(args) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .before_exec(|| unsafe { - match ::libc::fork() { - -1 => return Err(io::Error::last_os_error()), - 0 => (), - _ => ::libc::_exit(0), - } - - if ::libc::setsid() == -1 { - return Err(io::Error::last_os_error()); - } - - Ok(()) - }) - .spawn()? - .wait() - .map(|_| ()) -} - -#[cfg(windows)] -pub fn start_daemon<I, S>(program: &str, args: I) -> io::Result<()> -where - I: IntoIterator<Item = S>, - S: AsRef<OsStr>, -{ - // Setting all the I/O handles to null and setting the - // CREATE_NEW_PROCESS_GROUP and CREATE_NO_WINDOW has the effect - // that console applications will run without opening a new - // console window. - Command::new(program) - .args(args) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .creation_flags(CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW) - .spawn() - .map(|_| ()) -} - -#[cfg(test)] -mod tests { - use super::limit; - - #[test] - fn limit_works() { - assert_eq!(10, limit(10, 0, 100)); - assert_eq!(10, limit(5, 10, 100)); - assert_eq!(100, limit(1000, 10, 100)); - } -} diff --git a/src/window.rs b/src/window.rs deleted file mode 100644 index b6384600..00000000 --- a/src/window.rs +++ /dev/null @@ -1,497 +0,0 @@ -// Copyright 2016 Joe Wilm, The Alacritty Project Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -use std::convert::From; -use std::fmt::Display; - -use crate::gl; -use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; -#[cfg(not(any(target_os = "macos", windows)))] -use glutin::os::unix::EventsLoopExt; -#[cfg(windows)] -use glutin::Icon; -use glutin::{ - self, ContextBuilder, ControlFlow, Event, EventsLoop, MouseCursor, PossiblyCurrent, - WindowBuilder, -}; -#[cfg(windows)] -use image::ImageFormat; - -use crate::cli::Options; -use crate::config::{Decorations, StartupMode, WindowConfig}; - -#[cfg(windows)] -static WINDOW_ICON: &'static [u8] = include_bytes!("../extra/windows/alacritty.ico"); - -/// Default Alacritty name, used for window title and class. -pub const DEFAULT_NAME: &str = "Alacritty"; - -/// Window errors -#[derive(Debug)] -pub enum Error { - /// Error creating the window - ContextCreation(glutin::CreationError), - - /// Error manipulating the rendering context - Context(glutin::ContextError), -} - -/// Result of fallible operations concerning a Window. -type Result<T> = ::std::result::Result<T, Error>; - -/// A window which can be used for displaying the terminal -/// -/// Wraps the underlying windowing library to provide a stable API in Alacritty -pub struct Window { - event_loop: EventsLoop, - windowed_context: glutin::WindowedContext<PossiblyCurrent>, - mouse_visible: bool, - - /// Whether or not the window is the focused window. - pub is_focused: bool, -} - -/// Threadsafe APIs for the window -pub struct Proxy { - inner: glutin::EventsLoopProxy, -} - -/// Information about where the window is being displayed -/// -/// Useful for subsystems like the font rasterized which depend on DPI and scale -/// factor. -pub struct DeviceProperties { - /// Scale factor for pixels <-> points. - /// - /// This will be 1. on standard displays and may have a different value on - /// hidpi displays. - pub scale_factor: f64, -} - -impl ::std::error::Error for Error { - fn cause(&self) -> Option<&dyn (::std::error::Error)> { - match *self { - Error::ContextCreation(ref err) => Some(err), - Error::Context(ref err) => Some(err), - } - } - - fn description(&self) -> &str { - match *self { - Error::ContextCreation(ref _err) => "Error creating gl context", - Error::Context(ref _err) => "Error operating on render context", - } - } -} - -impl Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match *self { - Error::ContextCreation(ref err) => write!(f, "Error creating GL context; {}", err), - Error::Context(ref err) => write!(f, "Error operating on render context; {}", err), - } - } -} - -impl From<glutin::CreationError> for Error { - fn from(val: glutin::CreationError) -> Error { - Error::ContextCreation(val) - } -} - -impl From<glutin::ContextError> for Error { - fn from(val: glutin::ContextError) -> Error { - Error::Context(val) - } -} - -fn create_gl_window( - mut window: WindowBuilder, - event_loop: &EventsLoop, - srgb: bool, - dimensions: Option<LogicalSize>, -) -> Result<glutin::WindowedContext<PossiblyCurrent>> { - if let Some(dimensions) = dimensions { - window = window.with_dimensions(dimensions); - } - - let windowed_context = ContextBuilder::new() - .with_srgb(srgb) - .with_vsync(true) - .with_hardware_acceleration(None) - .build_windowed(window, event_loop)?; - - // Make the context current so OpenGL operations can run - let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, e)| e)? }; - - Ok(windowed_context) -} - -impl Window { - /// Create a new window - /// - /// This creates a window and fully initializes a window. - pub fn new( - event_loop: EventsLoop, - options: &Options, - window_config: &WindowConfig, - dimensions: Option<LogicalSize>, - ) -> Result<Window> { - let title = options.title.as_ref().map_or(DEFAULT_NAME, |t| t); - let class = options.class.as_ref().map_or(DEFAULT_NAME, |c| c); - let window_builder = Window::get_platform_window(title, class, window_config); - let windowed_context = - create_gl_window(window_builder.clone(), &event_loop, false, dimensions) - .or_else(|_| create_gl_window(window_builder, &event_loop, true, dimensions))?; - let window = windowed_context.window(); - window.show(); - - // Maximize window after mapping in X11 - #[cfg(not(any(target_os = "macos", windows)))] - { - if event_loop.is_x11() && window_config.startup_mode() == StartupMode::Maximized { - window.set_maximized(true); - } - } - - // Set window position - // - // TODO: replace `set_position` with `with_position` once available - // Upstream issue: https://github.com/tomaka/winit/issues/806 - let position = options.position().or_else(|| window_config.position()); - if let Some(position) = position { - let physical = PhysicalPosition::from((position.x, position.y)); - let logical = physical.to_logical(window.get_hidpi_factor()); - window.set_position(logical); - } - - if let StartupMode::Fullscreen = window_config.startup_mode() { - let current_monitor = window.get_current_monitor(); - window.set_fullscreen(Some(current_monitor)); - } - - #[cfg(target_os = "macos")] - { - if let StartupMode::SimpleFullscreen = window_config.startup_mode() { - use glutin::os::macos::WindowExt; - window.set_simple_fullscreen(true); - } - } - - // Text cursor - window.set_cursor(MouseCursor::Text); - - // Set OpenGL symbol loader. This call MUST be after window.make_current on windows. - gl::load_with(|symbol| windowed_context.get_proc_address(symbol) as *const _); - - let window = - Window { event_loop, windowed_context, mouse_visible: true, is_focused: false }; - - window.run_os_extensions(); - - Ok(window) - } - - /// Get some properties about the device - /// - /// Some window properties are provided since subsystems like font - /// rasterization depend on DPI and scale factor. - pub fn device_properties(&self) -> DeviceProperties { - DeviceProperties { scale_factor: self.window().get_hidpi_factor() } - } - - pub fn inner_size_pixels(&self) -> Option<LogicalSize> { - self.window().get_inner_size() - } - - pub fn set_inner_size(&mut self, size: LogicalSize) { - self.window().set_inner_size(size); - } - - #[inline] - pub fn hidpi_factor(&self) -> f64 { - self.window().get_hidpi_factor() - } - - #[inline] - pub fn create_window_proxy(&self) -> Proxy { - Proxy { inner: self.event_loop.create_proxy() } - } - - #[inline] - pub fn swap_buffers(&self) -> Result<()> { - self.windowed_context.swap_buffers().map_err(From::from) - } - - /// Poll for any available events - #[inline] - pub fn poll_events<F>(&mut self, func: F) - where - F: FnMut(Event), - { - self.event_loop.poll_events(func); - } - - #[inline] - pub fn resize(&self, size: PhysicalSize) { - self.windowed_context.resize(size); - } - - /// Block waiting for events - #[inline] - pub fn wait_events<F>(&mut self, func: F) - where - F: FnMut(Event) -> ControlFlow, - { - self.event_loop.run_forever(func); - } - - /// Set the window title - #[inline] - pub fn set_title(&self, title: &str) { - self.window().set_title(title); - } - - #[inline] - pub fn set_mouse_cursor(&self, cursor: MouseCursor) { - self.window().set_cursor(cursor); - } - - /// Set mouse cursor visible - pub fn set_mouse_visible(&mut self, visible: bool) { - if visible != self.mouse_visible { - self.mouse_visible = visible; - self.window().hide_cursor(!visible); - } - } - - #[cfg(not(any(target_os = "macos", windows)))] - pub fn get_platform_window( - title: &str, - class: &str, - window_config: &WindowConfig, - ) -> WindowBuilder { - use glutin::os::unix::WindowBuilderExt; - - let decorations = match window_config.decorations() { - Decorations::None => false, - _ => true, - }; - - WindowBuilder::new() - .with_title(title) - .with_visibility(false) - .with_transparency(true) - .with_decorations(decorations) - .with_maximized(window_config.startup_mode() == StartupMode::Maximized) - // X11 - .with_class(class.into(), DEFAULT_NAME.into()) - // Wayland - .with_app_id(class.into()) - } - - #[cfg(windows)] - pub fn get_platform_window( - title: &str, - _class: &str, - window_config: &WindowConfig, - ) -> WindowBuilder { - let icon = Icon::from_bytes_with_format(WINDOW_ICON, ImageFormat::ICO).unwrap(); - - let decorations = match window_config.decorations() { - Decorations::None => false, - _ => true, - }; - - WindowBuilder::new() - .with_title(title) - .with_visibility(cfg!(windows)) - .with_decorations(decorations) - .with_transparency(true) - .with_maximized(window_config.startup_mode() == StartupMode::Maximized) - .with_window_icon(Some(icon)) - } - - #[cfg(target_os = "macos")] - pub fn get_platform_window( - title: &str, - _class: &str, - window_config: &WindowConfig, - ) -> WindowBuilder { - use glutin::os::macos::WindowBuilderExt; - - let window = WindowBuilder::new() - .with_title(title) - .with_visibility(false) - .with_transparency(true) - .with_maximized(window_config.startup_mode() == StartupMode::Maximized); - - match window_config.decorations() { - Decorations::Full => window, - Decorations::Transparent => window - .with_title_hidden(true) - .with_titlebar_transparent(true) - .with_fullsize_content_view(true), - Decorations::Buttonless => window - .with_title_hidden(true) - .with_titlebar_buttons_hidden(true) - .with_titlebar_transparent(true) - .with_fullsize_content_view(true), - Decorations::None => window.with_titlebar_hidden(true), - } - } - - #[cfg(any( - target_os = "linux", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "openbsd" - ))] - pub fn set_urgent(&self, is_urgent: bool) { - use glutin::os::unix::WindowExt; - self.window().set_urgent(is_urgent); - } - - #[cfg(target_os = "macos")] - pub fn set_urgent(&self, is_urgent: bool) { - use glutin::os::macos::WindowExt; - self.window().request_user_attention(is_urgent); - } - - #[cfg(windows)] - pub fn set_urgent(&self, _is_urgent: bool) {} - - pub fn set_ime_spot(&self, pos: LogicalPosition) { - self.window().set_ime_spot(pos); - } - - #[cfg(not(any(target_os = "macos", target_os = "windows")))] - pub fn get_window_id(&self) -> Option<usize> { - use glutin::os::unix::WindowExt; - - match self.window().get_xlib_window() { - Some(xlib_window) => Some(xlib_window as usize), - None => None, - } - } - - #[cfg(any(target_os = "macos", target_os = "windows"))] - pub fn get_window_id(&self) -> Option<usize> { - None - } - - /// Hide the window - pub fn hide(&self) { - self.window().hide(); - } - - /// Fullscreens the window on the current monitor. - pub fn set_fullscreen(&self, fullscreen: bool) { - let glutin_window = self.window(); - if fullscreen { - let current_monitor = glutin_window.get_current_monitor(); - glutin_window.set_fullscreen(Some(current_monitor)); - } else { - glutin_window.set_fullscreen(None); - } - } - - #[cfg(target_os = "macos")] - pub fn set_simple_fullscreen(&self, fullscreen: bool) { - use glutin::os::macos::WindowExt; - self.window().set_simple_fullscreen(fullscreen); - } - - fn window(&self) -> &glutin::Window { - self.windowed_context.window() - } -} - -pub trait OsExtensions { - fn run_os_extensions(&self) {} -} - -#[cfg(not(any( - target_os = "linux", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "openbsd" -)))] -impl OsExtensions for Window {} - -#[cfg(any( - target_os = "linux", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "openbsd" -))] -impl OsExtensions for Window { - fn run_os_extensions(&self) { - use glutin::os::unix::WindowExt; - use libc::getpid; - use std::ffi::CStr; - use std::ptr; - use x11_dl::xlib::{self, PropModeReplace, XA_CARDINAL}; - - let xlib_display = self.window().get_xlib_display(); - let xlib_window = self.window().get_xlib_window(); - - if let (Some(xlib_window), Some(xlib_display)) = (xlib_window, xlib_display) { - let xlib = xlib::Xlib::open().expect("get xlib"); - - // Set _NET_WM_PID to process pid - unsafe { - let _net_wm_pid = CStr::from_ptr(b"_NET_WM_PID\0".as_ptr() as *const _); - let atom = (xlib.XInternAtom)(xlib_display as *mut _, _net_wm_pid.as_ptr(), 0); - let pid = getpid(); - - (xlib.XChangeProperty)( - xlib_display as _, - xlib_window as _, - atom, - XA_CARDINAL, - 32, - PropModeReplace, - &pid as *const i32 as *const u8, - 1, - ); - } - // Although this call doesn't actually pass any data, it does cause - // WM_CLIENT_MACHINE to be set. WM_CLIENT_MACHINE MUST be set if _NET_WM_PID is set - // (which we do above). - unsafe { - (xlib.XSetWMProperties)( - xlib_display as _, - xlib_window as _, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - 0, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ); - } - } - } -} - -impl Proxy { - /// Wakes up the event loop of the window - /// - /// This is useful for triggering a draw when the renderer would otherwise - /// be waiting on user input. - pub fn wakeup_event_loop(&self) { - self.inner.wakeup().unwrap(); - } -} |