aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ansi.rs1568
-rw-r--r--src/cli.rs243
-rw-r--r--src/config/bindings.rs233
-rw-r--r--src/config/mod.rs2749
-rw-r--r--src/cursor.rs99
-rw-r--r--src/display.rs560
-rw-r--r--src/event.rs595
-rw-r--r--src/event_loop.rs436
-rw-r--r--src/grid/mod.rs886
-rw-r--r--src/grid/row.rs264
-rw-r--r--src/grid/storage.rs922
-rw-r--r--src/grid/tests.rs292
-rw-r--r--src/index.rs406
-rw-r--r--src/input.rs1300
-rw-r--r--src/lib.rs61
-rw-r--r--src/locale.rs89
-rw-r--r--src/logging.rs198
-rw-r--r--src/macros.rs21
-rw-r--r--src/main.rs270
-rw-r--r--src/message_bar.rs473
-rw-r--r--src/meter.rs110
-rw-r--r--src/panic.rs53
-rw-r--r--src/renderer/mod.rs1625
-rw-r--r--src/renderer/rects.rs156
-rw-r--r--src/selection.rs571
-rw-r--r--src/sync.rs44
-rw-r--r--src/term/cell.rs205
-rw-r--r--src/term/color.rs224
-rw-r--r--src/term/mod.rs2442
-rw-r--r--src/tty/mod.rs96
-rw-r--r--src/tty/unix.rs405
-rw-r--r--src/tty/windows/conpty.rs289
-rw-r--r--src/tty/windows/mod.rs303
-rw-r--r--src/tty/windows/winpty.rs169
-rw-r--r--src/url.rs307
-rw-r--r--src/util.rs143
-rw-r--r--src/window.rs497
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 &params[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(&regular_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(&regular_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();
- }
-}