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
//! Describes a particular segment within a line of the interface.

use crate::cursor::CursorPosition;
use crate::update::UpdateStep;
use crate::interface::TTYInterface;
use crate::utility::{clear_rest_of_line, move_cursor_exact, render_segment};
use crate::result::{Result, TTYError};

/// Contains ANSI control sequences for formatting a segment's text.
#[derive(Clone)]
pub struct SegmentFormatting {
    /// Applied immediately before the segment's text.
    pub(crate) pre: String,
    /// Applied immediately after the segment's text.
    pub(crate) post: String,
}

impl SegmentFormatting {
    /// Create new segment formatting with the specified control sequences.
    pub fn new(pre: String, post: String) -> SegmentFormatting {
        SegmentFormatting { pre, post }
    }
}

/// Represents a segment of text within a line of the interface.
pub struct Segment {
    /// The segment's displayed text.
    pub(crate) text: String,
    /// Optionally, any formatting control sequences.
    pub(crate) format: Option<SegmentFormatting>,
}

impl Segment {
    /// Create a segment with the specified text.
    pub fn new(text: String) -> Segment {
        Segment { text, format: None }
    }

    /// Create a segment with the specified text and formatting.
    pub fn new_formatted(text: String, format: SegmentFormatting) -> Segment {
        Segment { text, format: Some(format) }
    }
}

/// Describes a staged segment update.
pub(crate) struct SetSegmentStep {
    pub(crate) line_index: usize,
    pub(crate) segment_index: usize,
    pub(crate) segment: Option<Segment>,
}

impl UpdateStep for SetSegmentStep {
    fn do_update(&mut self, interface: &mut TTYInterface, update_cursor: &mut CursorPosition) -> Result<()> {
        if self.line_index > interface.state.lines.len() - 1 {
            return Err(TTYError::LineOutOfBounds);
        }

        if self.segment_index > interface.state.lines[self.line_index].segments.len() {
            return Err(TTYError::SegmentOutOfBounds);
        }

        // This is updating an existing segment
        if self.segment_index < interface.state.lines[self.line_index].segments.len() {
            // Determine if the updated segment has a different text length
            let diff_length = interface.state.lines[self.line_index].segments[self.segment_index].text.len()
                != self.segment.as_ref().unwrap().text.len();

            // Update the interface state
            interface.state.lines[self.line_index].segments[self.segment_index] = self.segment.take().unwrap();

            // Handle rendering the segment
            let segment_start = interface.state.lines[self.line_index].get_segment_start(self.segment_index);
            move_cursor_exact(interface.writer, update_cursor, segment_start, self.line_index as u16)?;
            render_segment(interface.writer, update_cursor, &interface.state.lines[self.line_index].segments[self.segment_index])?;

            // If the segment's length differed, we need to re-render segments to the right of this one
            if diff_length {
                clear_rest_of_line(interface.writer)?;
                for segment in &interface.state.lines[self.line_index].segments[self.segment_index+1..] {
                    render_segment(interface.writer, update_cursor, segment)?;
                }
            }
        } else {
            // This is a completely new segment, so we can simply append
            interface.state.lines[self.line_index].segments.push(self.segment.take().unwrap());

            // Handle rendering the new segment
            let segment_start = interface.state.lines[self.line_index].get_segment_start(self.segment_index);
            move_cursor_exact(interface.writer, update_cursor, segment_start, self.line_index as u16)?;
            render_segment(interface.writer, update_cursor, &interface.state.lines[self.line_index].segments[self.segment_index])?;
        }

        Ok(())
    }
}

/// Describes a staged segment deletion
pub(crate) struct DeleteSegmentStep {
    pub(crate) line_index: usize,
    pub(crate) segment_index: usize,
}

impl UpdateStep for DeleteSegmentStep {
    fn do_update(&mut self, interface: &mut TTYInterface, update_cursor: &mut CursorPosition) -> Result<()> {
        if self.line_index > interface.state.lines.len() - 1 {
            return Err(TTYError::LineOutOfBounds);
        }

        if self.segment_index > interface.state.lines[self.line_index].segments.len() - 1 {
            return Err(TTYError::SegmentOutOfBounds);
        }

        // Identify the start of this segment and clear to the right
        let segment_start = interface.state.lines[self.line_index].get_segment_start(self.segment_index);
        move_cursor_exact(interface.writer, update_cursor, segment_start, self.line_index as u16)?;
        clear_rest_of_line(interface.writer)?;

        // Update the interface's state by removing this segment
        interface.state.lines[self.line_index].segments.remove(self.segment_index);

        // Re-render segments to the right of this one
        for segment in &interface.state.lines[self.line_index].segments[self.segment_index..] {
            render_segment(interface.writer, update_cursor, segment)?;
        }

        Ok(())
    }
}