1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! A scrollable, append-only buffer of lines.
use base::basic_types::*;
use base::{Cursor, Window, WrappingMode};
use input::{OperationResult, Scrollable};
use std::fmt;
use std::ops::Range;
use widget::{Demand, Demand2D, RenderingHints, Widget};

/// A scrollable, append-only buffer of lines.
pub struct LogViewer {
    storage: Vec<String>, // Invariant: always holds at least one line, does not contain newlines
    scrollback_position: Option<LineIndex>,
    scroll_step: usize,
}

impl LogViewer {
    /// Create an empty `LogViewer`. Add lines by writing to the viewer as `std::io::Write`.
    pub fn new() -> Self {
        let mut storage = Vec::new();
        storage.push(String::new()); //Fullfil invariant (at least one line)
        LogViewer {
            storage: storage,
            scrollback_position: None,
            scroll_step: 1,
        }
    }

    fn num_lines_stored(&self) -> usize {
        self.storage.len() // Per invariant: no newlines in storage
    }

    fn current_line_index(&self) -> LineIndex {
        self.scrollback_position.unwrap_or(LineIndex::new(
            self.num_lines_stored().checked_sub(1).unwrap_or(0),
        ))
    }

    /// Note: Do not insert newlines into the string using this
    fn active_line_mut(&mut self) -> &mut String {
        self.storage
            .last_mut()
            .expect("Invariant: At least one line")
    }

    fn view(&self, range: Range<LineIndex>) -> &[String] {
        &self.storage[range.start.raw_value()..range.end.raw_value()]
    }
}

impl fmt::Write for LogViewer {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        let mut s = s.to_owned();

        while let Some(newline_offset) = s.find('\n') {
            let mut line: String = s.drain(..(newline_offset + 1)).collect();
            line.pop(); //Remove the \n
            self.active_line_mut().push_str(&line);
            self.storage.push(String::new());
        }
        self.active_line_mut().push_str(&s);
        Ok(())
    }
}

impl Widget for LogViewer {
    fn space_demand(&self) -> Demand2D {
        Demand2D {
            width: Demand::at_least(1),
            height: Demand::at_least(1),
        }
    }
    fn draw(&self, mut window: Window, _: RenderingHints) {
        let height = window.get_height();
        if height == 0 {
            return;
        }

        // TODO: This does not work well when lines are wrapped, but we may want scrolling farther
        // than 1 line per event
        // self.scroll_step = ::std::cmp::max(1, height.checked_sub(1).unwrap_or(1));

        let y_start = height - 1;
        let mut cursor = Cursor::new(&mut window)
            .position(ColIndex::new(0), y_start.from_origin())
            .wrapping_mode(WrappingMode::Wrap);
        let end_line = self.current_line_index();
        let start_line =
            LineIndex::new(end_line.raw_value().checked_sub(height.into()).unwrap_or(0));
        for line in self.view(start_line..(end_line + 1)).iter().rev() {
            let num_auto_wraps = cursor.num_expected_wraps(&line) as i32;
            cursor.move_by(ColDiff::new(0), RowDiff::new(-num_auto_wraps));
            cursor.writeln(&line);
            cursor.move_by(ColDiff::new(0), RowDiff::new(-num_auto_wraps) - 2);
        }
    }
}

impl Scrollable for LogViewer {
    fn scroll_forwards(&mut self) -> OperationResult {
        let current = self.current_line_index();
        let candidate = current + self.scroll_step;
        self.scrollback_position = if candidate.raw_value() < self.num_lines_stored() {
            Some(candidate)
        } else {
            None
        };
        if self.scrollback_position.is_some() {
            Ok(())
        } else {
            Err(())
        }
    }
    fn scroll_backwards(&mut self) -> OperationResult {
        let current = self.current_line_index();
        let op_res = if current.raw_value() != 0 {
            Ok(())
        } else {
            Err(())
        };
        self.scrollback_position = Some(
            current
                .checked_sub(self.scroll_step)
                .unwrap_or(LineIndex::new(0)),
        );
        op_res
    }
    fn scroll_to_beginning(&mut self) -> OperationResult {
        if Some(LineIndex::new(0)) == self.scrollback_position {
            Err(())
        } else {
            self.scrollback_position = Some(LineIndex::new(0));
            Ok(())
        }
    }
    fn scroll_to_end(&mut self) -> OperationResult {
        if self.scrollback_position.is_none() {
            Err(())
        } else {
            self.scrollback_position = None;
            Ok(())
        }
    }
}