pepper 0.31.0

A simple and opinionated modal code editor for your terminal
Documentation
use std::{
    io,
    mem::ManuallyDrop,
    process::{Command, Stdio},
};

use crate::{client::ClientHandle, editor_utils::parse_process_command, plugin::PluginHandle};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyCode {
    None,
    Backspace,
    Left,
    Right,
    Up,
    Down,
    Home,
    End,
    PageUp,
    PageDown,
    Delete,
    F(u8),
    Char(char),
    Esc,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Key {
    pub code: KeyCode,
    pub shift: bool,
    pub control: bool,
    pub alt: bool,
}

impl Default for Key {
    fn default() -> Self {
        Self {
            code: KeyCode::None,
            shift: false,
            control: false,
            alt: false,
        }
    }
}

pub enum PlatformEvent {
    Idle,
    ConnectionOpen {
        handle: ClientHandle,
    },
    ConnectionClose {
        handle: ClientHandle,
    },
    ConnectionOutput {
        handle: ClientHandle,
        buf: PooledBuf,
    },
    ProcessSpawned {
        tag: ProcessTag,
        handle: PlatformProcessHandle,
    },
    ProcessOutput {
        tag: ProcessTag,
        buf: PooledBuf,
    },
    ProcessExit {
        tag: ProcessTag,
    },
    IpcConnected {
        tag: IpcTag,
        handle: PlatformIpcHandle,
    },
    IpcOutput {
        tag: IpcTag,
        buf: PooledBuf,
    },
    IpcClose {
        tag: IpcTag,
    },
}

pub enum IpcReadMode {
    ByteStream,
    MessageStream,
}

pub enum PlatformRequest {
    Quit,
    Redraw,
    WriteToClient {
        handle: ClientHandle,
        buf: PooledBuf,
    },
    CloseClient {
        handle: ClientHandle,
    },
    SpawnProcess {
        tag: ProcessTag,
        command: Command,
        buf_len: usize,
    },
    WriteToProcess {
        handle: PlatformProcessHandle,
        buf: PooledBuf,
    },
    CloseProcessInput {
        handle: PlatformProcessHandle,
    },
    KillProcess {
        handle: PlatformProcessHandle,
    },
    ConnectToIpc {
        tag: IpcTag,
        path: PooledBuf,
        read: bool,
        write: bool,
        read_mode: IpcReadMode,
        buf_len: usize,
    },
    WriteToIpc {
        handle: PlatformIpcHandle,
        buf: PooledBuf,
    },
    CloseIpc {
        handle: PlatformIpcHandle,
    },
}

#[derive(Clone, Copy)]
pub enum ProcessTag {
    Ignored,
    Buffer(u32),
    PickerEntries,
    Plugin {
        plugin_handle: PluginHandle,
        id: u32,
    },
}

#[derive(Clone, Copy)]
pub struct IpcTag {
    pub plugin_handle: PluginHandle,
    pub id: u32,
}

#[derive(Clone, Copy)]
pub struct PlatformProcessHandle(pub u8);

#[derive(Clone, Copy)]
pub struct PlatformIpcHandle(pub u8);

#[derive(Default)]
pub struct PlatformRequestCollection {
    pending_requests: Vec<PlatformRequest>,
}
impl PlatformRequestCollection {
    pub fn enqueue(&mut self, request: PlatformRequest) {
        self.pending_requests.push(request);
    }

    pub fn drain(&mut self) -> impl '_ + Iterator<Item = PlatformRequest> {
        self.pending_requests.drain(..)
    }
}

#[derive(Default)]
pub struct Platform {
    pub requests: PlatformRequestCollection,

    read_from_clipboard_fn: Option<fn(&mut String)>,
    write_to_clipboard_fn: Option<fn(&str)>,

    pub buf_pool: BufPool,

    internal_clipboard: String,
    pub copy_command: String,
    pub paste_command: String,
}
impl Platform {
    pub fn set_clipboard_api(
        &mut self,
        read_from_clipboard_fn: fn(&mut String),
        write_to_clipboard_fn: fn(&str),
    ) {
        self.read_from_clipboard_fn = Some(read_from_clipboard_fn);
        self.write_to_clipboard_fn = Some(write_to_clipboard_fn);
    }

    pub fn read_from_clipboard(&self, text: &mut String) {
        if let Some(mut command) = parse_process_command(&self.paste_command) {
            command.stdin(Stdio::null());
            command.stdout(Stdio::piped());
            command.stderr(Stdio::null());
            if let Ok(output) = command.output() {
                if let Ok(output) = String::from_utf8(output.stdout) {
                    text.push_str(&output);
                }
            }
        } else if let Some(read_from_clipboard) = self.read_from_clipboard_fn {
            read_from_clipboard(text);
        } else {
            text.push_str(&self.internal_clipboard);
        }
    }

    pub fn write_to_clipboard(&mut self, text: &str) {
        if let Some(mut command) = parse_process_command(&self.copy_command) {
            command.stdin(Stdio::piped());
            command.stdout(Stdio::null());
            command.stderr(Stdio::null());
            if let Ok(mut child) = command.spawn() {
                if let Some(mut stdin) = child.stdin.take() {
                    use io::Write;
                    let _ = stdin.write_all(text.as_bytes());
                }
                let _ = child.wait();
            }
        } else if let Some(write_to_clipboard) = self.write_to_clipboard_fn {
            write_to_clipboard(text);
        } else {
            self.internal_clipboard.clear();
            self.internal_clipboard.push_str(text);
        }
    }
}

pub struct PooledBuf(Vec<u8>);
impl PooledBuf {
    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }

    pub fn drain_start(&mut self, start: usize) {
        self.0.drain(..start);
    }

    pub fn write(&mut self) -> &mut Vec<u8> {
        let buf = &mut self.0;
        buf.clear();
        buf
    }

    pub fn write_no_clear(&mut self) -> &mut Vec<u8> {
        &mut self.0
    }

    pub fn write_with_len(&mut self, len: usize) -> &mut Vec<u8> {
        let buf = &mut self.0;
        buf.resize(len, 0);
        buf
    }
}
impl Drop for PooledBuf {
    fn drop(&mut self) {
        panic!("buf was dropped outside of a pool");
    }
}

#[derive(Default)]
pub struct BufPool {
    pool: Vec<ManuallyDrop<PooledBuf>>,
}
impl BufPool {
    pub fn acquire(&mut self) -> PooledBuf {
        match self.pool.pop() {
            Some(buf) => ManuallyDrop::into_inner(buf),
            None => PooledBuf(Vec::new()),
        }
    }

    pub fn release(&mut self, buf: PooledBuf) {
        self.pool.push(ManuallyDrop::new(buf));
    }
}

pub fn drop_request(buf_pool: &mut BufPool, request: PlatformRequest) {
    match request {
        PlatformRequest::WriteToClient { buf, .. }
        | PlatformRequest::WriteToProcess { buf, .. }
        | PlatformRequest::WriteToIpc { buf, .. } => {
            buf_pool.release(buf);
        }
        PlatformRequest::Quit
        | PlatformRequest::Redraw
        | PlatformRequest::CloseClient { .. }
        | PlatformRequest::SpawnProcess { .. }
        | PlatformRequest::CloseProcessInput { .. }
        | PlatformRequest::KillProcess { .. }
        | PlatformRequest::ConnectToIpc { .. }
        | PlatformRequest::CloseIpc { .. } => (),
    }
}