use hjkl_buffer::Viewport;
use hjkl_engine::{CursorShape, Host};
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BuffrBufferId(pub u64);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BuffrEditIntent {
RequestAutocomplete,
SwitchBuffer(BuffrBufferId),
PassThrough,
}
#[derive(Debug)]
pub struct BuffrHost {
pub last_cursor_shape: CursorShape,
clipboard_cache: Option<String>,
clipboard_outbox: Vec<String>,
started: Instant,
intents: Vec<BuffrEditIntent>,
viewport: Viewport,
}
impl Default for BuffrHost {
fn default() -> Self {
Self::new()
}
}
impl BuffrHost {
pub fn new() -> Self {
Self {
last_cursor_shape: CursorShape::Block,
clipboard_cache: None,
clipboard_outbox: Vec::new(),
started: Instant::now(),
intents: Vec::new(),
viewport: Viewport::new(),
}
}
pub fn set_viewport_size(&mut self, width: u16, height: u16) {
self.viewport.width = width;
self.viewport.height = height;
}
pub fn set_clipboard_cache(&mut self, text: Option<String>) {
self.clipboard_cache = text;
}
pub fn drain_clipboard_outbox(&mut self) -> Vec<String> {
std::mem::take(&mut self.clipboard_outbox)
}
pub fn drain_intents(&mut self) -> Vec<BuffrEditIntent> {
std::mem::take(&mut self.intents)
}
}
impl Host for BuffrHost {
type Intent = BuffrEditIntent;
fn write_clipboard(&mut self, text: String) {
self.clipboard_outbox.push(text);
}
fn read_clipboard(&mut self) -> Option<String> {
self.clipboard_cache.clone()
}
fn now(&self) -> std::time::Duration {
self.started.elapsed()
}
fn prompt_search(&mut self) -> Option<String> {
None
}
fn emit_cursor_shape(&mut self, shape: CursorShape) {
self.last_cursor_shape = shape;
}
fn emit_intent(&mut self, intent: Self::Intent) {
self.intents.push(intent);
}
fn viewport(&self) -> &Viewport {
&self.viewport
}
fn viewport_mut(&mut self) -> &mut Viewport {
&mut self.viewport
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clipboard_outbox_drains() {
let mut host = BuffrHost::new();
host.write_clipboard("foo".into());
host.write_clipboard("bar".into());
let drained = host.drain_clipboard_outbox();
assert_eq!(drained, vec!["foo".to_string(), "bar".to_string()]);
assert!(host.drain_clipboard_outbox().is_empty());
}
#[test]
fn read_clipboard_uses_cache() {
let mut host = BuffrHost::new();
assert_eq!(host.read_clipboard(), None);
host.set_clipboard_cache(Some("payload".into()));
assert_eq!(host.read_clipboard().as_deref(), Some("payload"));
}
#[test]
fn intents_drain() {
let mut host = BuffrHost::new();
host.emit_intent(BuffrEditIntent::RequestAutocomplete);
host.emit_intent(BuffrEditIntent::PassThrough);
let drained = host.drain_intents();
assert_eq!(drained.len(), 2);
assert!(host.drain_intents().is_empty());
}
#[test]
fn now_advances() {
let host = BuffrHost::new();
let t0 = host.now();
std::thread::sleep(std::time::Duration::from_millis(1));
let t1 = host.now();
assert!(t1 > t0);
}
#[test]
fn cursor_shape_recorded() {
let mut host = BuffrHost::new();
assert_eq!(host.last_cursor_shape, CursorShape::Block);
host.emit_cursor_shape(CursorShape::Bar);
assert_eq!(host.last_cursor_shape, CursorShape::Bar);
}
#[test]
fn satisfies_host_trait() {
fn assert_host<H: Host>() {}
assert_host::<BuffrHost>();
}
#[test]
fn viewport_defaults_zero_then_round_trips() {
let mut host = BuffrHost::new();
assert_eq!(host.viewport().width, 0);
assert_eq!(host.viewport().height, 0);
host.set_viewport_size(120, 40);
assert_eq!(host.viewport().width, 120);
assert_eq!(host.viewport().height, 40);
host.viewport_mut().top_row = 5;
assert_eq!(host.viewport().top_row, 5);
}
}