<commit_before>// Copyright 2016 <NAME>, 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::{min, max, Ordering};
use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull};

use index::{self, Point, Line, Column, IndexRange};
use selection::Selection;

mod row;
pub use self::row::Row;

#[cfg(test)]
mod tests;

mod storage;
use self::storage::Storage;

/// 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 {
        self.cols.eq(&other.cols) &&
            self.lines.eq(&other.lines) &&
            self.raw.eq(&other.raw)
    }
}

/// 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<Row<T>>,

    /// Number of columns
    cols: index::Column,

    /// Number of lines.
    ///
    /// Invariant: lines is equivalent to raw.len()
    lines: index::Line,

    /// Template row.
    ///
    /// This is used to quickly populate new lines and clear recycled lines
    /// during scroll wrapping.
    #[serde(skip)]
    template_row: Row<T>,

    /// Template cell for populating template_row
    #[serde(skip)]
    template: T,

    /// 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.
    display_offset: usize,

    /// An limit on how far back it's possible to scroll
    scroll_limit: usize,

    /// Selected region
    #[serde(skip)]
    pub selection: Option<Selection>,
}

pub struct GridIterator<'a, T: 'a> {
    /// Immutable grid reference
    grid: &'a Grid<T>,

    /// Current position of the iterator within the grid.
    pub cur: Point<usize>,

    /// Bottom of screen (buffer)
    bot: usize,

    /// Top of screen (buffer)
    top: usize,
}

pub enum Scroll {
    Lines(isize),
    PageUp,
    PageDown,
    Top,
    Bottom,
}

impl<T: Copy + Clone> Grid<T> {
    pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid<T> {
        let mut raw = Storage::with_capacity(*lines + scrollback, lines);
        let template_row = Row::new(cols, &template);

        // Allocate all lines in the buffer, including scrollback history
        //
        // TODO (jwilm) Allocating each line at this point is expensive and
        // delays startup. A nice solution might be having `Row` delay
        // allocation until it's actually used.
        for _ in 0..raw.capacity() {
            raw.push(template_row.clone());
        }

        Grid {
            raw,
            cols,
            lines,
            template_row,
            template,
            display_offset: 0,
            scroll_limit: 0,
            selection: 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_to_visible(&self, point: Point<usize>) -> Point {
        Point {
            line: self.buffer_line_to_visible(point.line).expect("Line not visible"),
            col: point.col
        }
    }

    pub fn buffer_line_to_visible(&self, line: usize) -> Option<Line> {
        if line >= self.display_offset {
            self.offset_to_line(line - self.display_offset)
        } else {
            None
        }
    }

    pub fn visible_line_to_buffer(&self, line: Line) -> usize {
        self.line_to_offset(line) + self.display_offset
    }

    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) {
        // 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),
            Ordering::Greater => self.shrink_lines(lines),
            Ordering::Equal => (),
        }

        match self.cols.cmp(&cols) {
            Ordering::Less => self.grow_cols(cols),
            Ordering::Greater => self.shrink_cols(cols),
            Ordering::Equal => (),
        }
    }

    fn increase_scroll_limit(&mut self, count: usize) {
        self.scroll_limit = min(self.scroll_limit + count, self.raw.len() - *self.lines);
    }

    fn decrease_scroll_limit(&mut self, count: usize) {
        self.scroll_limit = self.scroll_limit.saturating_sub(count);
    }

    /// Add lines to the visible area
    ///
    /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the
    /// bottom of the screen as long as there is scrollback available. Once
    /// scrollback is exhausted, new lines are simply added to the bottom of the
    /// screen.
    ///
    /// Alacritty takes a different approach. Rather than trying to move with
    /// the scrollback, we simply pull additional lines from the back of the
    /// buffer in order to populate the new area.
    fn grow_lines(&mut self, new_line_count: index::Line) {
        let previous_scroll_limit = self.scroll_limit;
        let lines_added = new_line_count - self.lines;

        // Need to "resize" before updating buffer
        self.raw.set_visible_lines(new_line_count);
        self.lines = new_line_count;

        // Add new lines to bottom
        self.scroll_up(&(Line(0)..new_line_count), lines_added);

        self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added);
    }

    fn grow_cols(&mut self, cols: index::Column) {
        for row in self.raw.iter_mut() {
            row.grow(cols, &self.template);
        }

        // Update self cols
        self.cols = cols;

        // Also update template_row to be the correct length
        self.template_row.grow(cols, &self.template);
    }

    /// 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) {
        // TODO handle disabled scrollback
        // while index::Line(self.raw.len()) != lines {
        //     self.raw.pop();
        // }

        let prev = self.lines;

        self.selection = None;
        self.raw.rotate(*prev as isize - *target as isize);
        self.raw.set_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)
    }

    pub fn offset_to_line(&self, offset: usize) -> Option<Line> {
        if offset < *self.num_lines() {
            Some(self.lines - offset - 1)
        } else {
            None
        }
    }

    #[inline]
    pub fn scroll_down(&mut self, region: &Range<index::Line>, positions: index::Line) {
        // 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.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(&self.template_row);
            }
        } 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(&self.template_row);
            }
        }
    }

    /// scroll_up moves lines at the bottom towards the top
    ///<commit_msg>Fix regression with scrolling regions<commit_after>// Copyright 2016 <NAME>, 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::{min, max, Ordering};
use std::ops::{Deref, Range, Index, IndexMut, RangeTo, RangeFrom, RangeFull};

use index::{self, Point, Line, Column, IndexRange};
use selection::Selection;

mod row;
pub use self::row::Row;

#[cfg(test)]
mod tests;

mod storage;
use self::storage::Storage;

/// 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 {
        self.cols.eq(&other.cols) &&
            self.lines.eq(&other.lines) &&
            self.raw.eq(&other.raw)
    }
}

/// 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<Row<T>>,

    /// Number of columns
    cols: index::Column,

    /// Number of lines.
    ///
    /// Invariant: lines is equivalent to raw.len()
    lines: index::Line,

    /// Template row.
    ///
    /// This is used to quickly populate new lines and clear recycled lines
    /// during scroll wrapping.
    #[serde(skip)]
    template_row: Row<T>,

    /// Template cell for populating template_row
    #[serde(skip)]
    template: T,

    /// 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.
    display_offset: usize,

    /// An limit on how far back it's possible to scroll
    scroll_limit: usize,

    /// Selected region
    #[serde(skip)]
    pub selection: Option<Selection>,
}

pub struct GridIterator<'a, T: 'a> {
    /// Immutable grid reference
    grid: &'a Grid<T>,

    /// Current position of the iterator within the grid.
    pub cur: Point<usize>,

    /// Bottom of screen (buffer)
    bot: usize,

    /// Top of screen (buffer)
    top: usize,
}

pub enum Scroll {
    Lines(isize),
    PageUp,
    PageDown,
    Top,
    Bottom,
}

impl<T: Copy + Clone> Grid<T> {
    pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid<T> {
        let mut raw = Storage::with_capacity(*lines + scrollback, lines);
        let template_row = Row::new(cols, &template);

        // Allocate all lines in the buffer, including scrollback history
        //
        // TODO (jwilm) Allocating each line at this point is expensive and
        // delays startup. A nice solution might be having `Row` delay
        // allocation until it's actually used.
        for _ in 0..raw.capacity() {
            raw.push(template_row.clone());
        }

        Grid {
            raw,
            cols,
            lines,
            template_row,
            template,
            display_offset: 0,
            scroll_limit: 0,
            selection: 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_to_visible(&self, point: Point<usize>) -> Point {
        Point {
            line: self.buffer_line_to_visible(point.line).expect("Line not visible"),
            col: point.col
        }
    }

    pub fn buffer_line_to_visible(&self, line: usize) -> Option<Line> {
        if line >= self.display_offset {
            self.offset_to_line(line - self.display_offset)
        } else {
            None
        }
    }

    pub fn visible_line_to_buffer(&self, line: Line) -> usize {
        self.line_to_offset(line) + self.display_offset
    }

    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) {
        // 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),
            Ordering::Greater => self.shrink_lines(lines),
            Ordering::Equal => (),
        }

        match self.cols.cmp(&cols) {
            Ordering::Less => self.grow_cols(cols),
            Ordering::Greater => self.shrink_cols(cols),
            Ordering::Equal => (),
        }
    }

    fn increase_scroll_limit(&mut self, count: usize) {
        self.scroll_limit = min(self.scroll_limit + count, self.raw.len() - *self.lines);
    }

    fn decrease_scroll_limit(&mut self, count: usize) {
        self.scroll_limit = self.scroll_limit.saturating_sub(count);
    }

    /// Add lines to the visible area
    ///
    /// The behavior in Terminal.app and iTerm.app is to keep the cursor at the
    /// bottom of the screen as long as there is scrollback available. Once
    /// scrollback is exhausted, new lines are simply added to the bottom of the
    /// screen.
    ///
    /// Alacritty takes a different approach. Rather than trying to move with
    /// the scrollback, we simply pull additional lines from the back of the
    /// buffer in order to populate the new area.
    fn grow_lines(&mut self, new_line_count: index::Line) {
        let previous_scroll_limit = self.scroll_limit;
        let lines_added = new_line_count - self.lines;

        // Need to "resize" before updating buffer
        self.raw.set_visible_lines(new_line_count);
        self.lines = new_line_count;

        // Add new lines to bottom
        self.scroll_up(&(Line(0)..new_line_count), lines_added);

        self.scroll_limit = self.scroll_limit.saturating_sub(*lines_added);
    }

    fn grow_cols(&mut self, cols: index::Column) {
        for row in self.raw.iter_mut() {
            row.grow(cols, &self.template);
        }

        // Update self cols
        self.cols = cols;

        // Also update template_row to be the correct length
        self.template_row.grow(cols, &self.template);
    }

    /// 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) {
        // TODO handle disabled scrollback
        // while index::Line(self.raw.len()) != lines {
        //     self.raw.pop();
        // }

        let prev = self.lines;

        self.selection = None;
        self.raw.rotate(*prev as isize - *target as isize);
        self.raw.set_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)
    }

    pub fn offset_to_line(&self, offset: usize) -> Option<Line> {
        if offset < *self.num_lines() {
            Some(self.lines - offset - 1)
        } else {
            None
        }
    }

    #[inline]
    pub fn scroll_down(&mut self, region: &Range<index::Line>, positions: index::Line) {
        // 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.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(&self.template_row);
            }
        } 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(&self.template_row);
            }
        }
    }

    /// scroll_up moves lines at the bottom towards the top
    ///
{'fraction_non_alphanumeric': 0.11320843311264497, 'fraction_numerical': 0.001972108747710945, 'mean_word_length': 1.9642310368823939, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 93, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6047401', 'n_tokens_mistral': 6126, 'n_tokens_neox': 5934, 'n_words': 1910}