thoth-cli 0.1.87

A terminal scratchpad akin to Heynote
Documentation
#[derive(Clone)]
pub struct CodeBlockPopup {
    pub code_blocks: Vec<CodeBlock>,
    pub filtered_blocks: Vec<CodeBlock>,
    pub selected_index: usize,
    pub visible: bool,
    pub scroll_offset: usize,
}

#[derive(Clone)]
pub struct CodeBlock {
    pub content: String,
    pub language: String,
    pub start_line: usize,
    pub end_line: usize,
}

impl CodeBlock {
    pub fn new(content: String, language: String, start_line: usize, end_line: usize) -> Self {
        Self {
            content,
            language,
            start_line,
            end_line,
        }
    }
}

impl Default for CodeBlockPopup {
    fn default() -> Self {
        Self::new()
    }
}

impl CodeBlockPopup {
    pub fn new() -> Self {
        CodeBlockPopup {
            code_blocks: Vec::new(),
            filtered_blocks: Vec::new(),
            selected_index: 0,
            visible: false,
            scroll_offset: 0,
        }
    }

    pub fn set_code_blocks(&mut self, code_blocks: Vec<CodeBlock>) {
        self.code_blocks = code_blocks;
        self.filtered_blocks = self.code_blocks.clone();
        self.selected_index = 0;
        self.scroll_offset = 0;
    }

    pub fn move_selection_up(&mut self, visible_items: usize) {
        if self.filtered_blocks.is_empty() {
            return;
        }

        if self.selected_index > 0 {
            self.selected_index -= 1;
        } else {
            self.selected_index = self.filtered_blocks.len() - 1;
        }

        if self.selected_index <= self.scroll_offset {
            self.scroll_offset = self.selected_index;
        }
        if self.selected_index == self.filtered_blocks.len() - 1 {
            self.scroll_offset = self.filtered_blocks.len().saturating_sub(visible_items);
        }
    }

    pub fn move_selection_down(&mut self, visible_items: usize) {
        if self.filtered_blocks.is_empty() {
            return;
        }

        if self.selected_index < self.filtered_blocks.len() - 1 {
            self.selected_index += 1;
        } else {
            self.selected_index = 0;
            self.scroll_offset = 0;
        }

        let max_scroll = self.filtered_blocks.len().saturating_sub(visible_items);
        if self.selected_index >= self.scroll_offset + visible_items {
            self.scroll_offset = (self.selected_index + 1).saturating_sub(visible_items);
            if self.scroll_offset > max_scroll {
                self.scroll_offset = max_scroll;
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_code_block_popup() {
        let popup = CodeBlockPopup::new();
        assert!(popup.code_blocks.is_empty());
        assert_eq!(popup.selected_index, 0);
        assert!(!popup.visible);
    }

    #[test]
    fn test_set_code_blocks() {
        let mut popup = CodeBlockPopup::new();
        let blocks = vec![
            CodeBlock::new("fn main() {}".to_string(), "rust".to_string(), 0, 2),
            CodeBlock::new("def hello(): pass".to_string(), "python".to_string(), 3, 5),
        ];
        popup.set_code_blocks(blocks);
        assert_eq!(popup.code_blocks.len(), 2);
        assert_eq!(popup.code_blocks[0].language, "rust");
        assert_eq!(popup.code_blocks[1].language, "python");
    }

    #[test]
    fn test_move_selection() {
        let mut popup = CodeBlockPopup::new();
        let blocks = vec![
            CodeBlock::new("Block 1".to_string(), "rust".to_string(), 0, 2),
            CodeBlock::new("Block 2".to_string(), "python".to_string(), 3, 5),
            CodeBlock::new("Block 3".to_string(), "js".to_string(), 6, 8),
        ];
        popup.set_code_blocks(blocks);

        popup.move_selection_down(2);
        assert_eq!(popup.selected_index, 1);

        popup.move_selection_up(2);
        assert_eq!(popup.selected_index, 0);

        popup.move_selection_up(2);
        assert_eq!(popup.selected_index, 2);

        popup.move_selection_down(2);
        assert_eq!(popup.selected_index, 0);
    }
}