pepper 0.31.0

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

use crate::ResourceFile;

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

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 MAIN_HELP_PAGES: HelpPages = HelpPages::new(&[
    ResourceFile {
        name: "help.md",
        content: include_str!("../rc/help.md"),
    },
    ResourceFile {
        name: "changelog.md",
        content: include_str!("../rc/changelog.md"),
    },
    ResourceFile {
        name: "command_reference.md",
        content: include_str!("../rc/command_reference.md"),
    },
    ResourceFile {
        name: "expansion_reference.md",
        content: include_str!("../rc/expansion_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"),
    },
    crate::DEFAULT_CONFIGS,
    crate::DEFAULT_SYNTAXES,
]);

pub(crate) fn add_help_pages(pages: &'static [ResourceFile]) {
    if pages.is_empty() {
        return;
    }

    let pages = Box::into_raw(Box::new(HelpPages::new(pages)));
    let mut current = &MAIN_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 help_page_names() -> impl Iterator<Item = &'static str> {
    HelpPageIterator::new().map(|r| r.name)
}

#[derive(Default)]
pub(crate) struct HelpPageName<'a>(&'a str);
pub(crate) fn parse_help_page_name(page_name: &str) -> Option<HelpPageName> {
    let page_name = page_name.strip_prefix(HELP_PREFIX)?;
    Some(HelpPageName(page_name))
}

pub(crate) fn open(page_name: HelpPageName) -> (&'static str, impl io::BufRead) {
    let page_name = page_name.0;
    for page in HelpPageIterator::new() {
        if page_name == page.name {
            return (page.name, io::Cursor::new(page.content));
        }
    }
    let main_page = &MAIN_HELP_PAGES.pages[0];
    (main_page.name, io::Cursor::new(main_page.content))
}

struct HelpPageIterator {
    current: &'static HelpPages,
    index: usize,
}
impl HelpPageIterator {
    pub fn new() -> Self {
        Self {
            current: &MAIN_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 };
                    self.index = 0;
                }
            }
        }
    }
}