pepper 0.15.1

A simple and opinionated modal code editor for your terminal
Documentation
use std::{
    io,
    path::Path,
    sync::atomic::{AtomicPtr, Ordering},
};

use crate::{buffer_position::BufferPosition, ResourceFile};

pub const HELP_PREFIX: &str = "help://";

pub struct HelpPages {
    pages: &'static [ResourceFile],
    next: AtomicPtr<HelpPages>,
}
impl HelpPages {
    pub const fn new(pages: &'static [ResourceFile]) -> Self {
        Self {
            pages,
            next: AtomicPtr::new(std::ptr::null_mut()),
        }
    }
}

static HELP_PAGES: HelpPages = HelpPages::new(&[
    ResourceFile {
        name: "command_reference.md",
        content: include_str!("../rc/command_reference.md"),
    },
    ResourceFile {
        name: "bindings.md",
        content: include_str!("../rc/bindings.md"),
    },
    ResourceFile {
        name: "language_syntax_definitions.md",
        content: include_str!("../rc/language_syntax_definitions.md"),
    },
    ResourceFile {
        name: "config_recipes.md",
        content: include_str!("../rc/config_recipes.md"),
    },
    ResourceFile {
        name: "help.md",
        content: include_str!("../rc/help.md"),
    },
]);

pub(crate) fn add_help_pages(pages: &'static HelpPages) {
    let pages = pages as *const _ as *mut _;
    let mut current = &HELP_PAGES;
    loop {
        match current.next.compare_exchange(
            std::ptr::null_mut(),
            pages,
            Ordering::Relaxed,
            Ordering::Relaxed,
        ) {
            Ok(_) => break,
            Err(next) => current = unsafe { &*next },
        }
    }
}

pub(crate) fn main_help_name() -> &'static str {
    HELP_PAGES.pages[HELP_PAGES.pages.len() - 1].name
}

pub(crate) fn open(path: &Path) -> Option<impl io::BufRead> {
    let path = match path.to_str().and_then(|p| p.strip_prefix(HELP_PREFIX)) {
        Some(path) => path,
        None => return None,
    };
    for page in HelpPageIterator::new() {
        if path == page.name {
            return Some(io::Cursor::new(page.content));
        }
    }
    None
}

pub(crate) fn search(keyword: &str) -> Option<(&'static str, BufferPosition)> {
    let mut last_match = None;
    for page in HelpPageIterator::new() {
        if keyword == page.name.trim_end_matches(".md") {
            return Some((page.name, BufferPosition::zero()));
        }

        for (line_index, line) in page.content.lines().enumerate() {
            if let Some(column_index) = line.find(keyword) {
                let position = BufferPosition::line_col(line_index as _, column_index as _);
                if line.starts_with('#') {
                    return Some((page.name, position));
                } else {
                    last_match = Some((page.name, position));
                }
            }
        }
    }

    last_match
}

struct HelpPageIterator {
    current: &'static HelpPages,
    index: usize,
}
impl HelpPageIterator {
    pub fn new() -> Self {
        Self {
            current: &HELP_PAGES,
            index: 0,
        }
    }
}
impl Iterator for HelpPageIterator {
    type Item = ResourceFile;
    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if self.index < self.current.pages.len() {
                let page = self.current.pages[self.index];
                self.index += 1;
                break Some(page);
            } else {
                let next = self.current.next.load(Ordering::Relaxed);
                if next.is_null() {
                    break None;
                } else {
                    self.current = unsafe { &*next };
                }
            }
        }
    }
}