use crate::{
error::Result,
types::{Cell, ModalItem, RemoteCommand},
};
use parking_lot::Mutex;
use serde::Deserialize;
use std::{collections::HashMap, sync::Arc};
#[derive(Debug)]
pub struct PluginSharedState {
pub cells: Vec<Cell>,
pub cols: u16,
pub rows: u16,
pub cursor: (u16, u16),
pub env: HashMap<String, String>,
pub data: HashMap<String, String>,
pub commands: Vec<ModalItem>,
}
impl PluginSharedState {
pub fn new(cols: u16, rows: u16) -> Self {
let size = (cols as usize) * (rows as usize);
Self {
cells: vec![Cell::default(); size],
cols,
rows,
cursor: (0, 0),
env: std::env::vars().collect(),
data: HashMap::new(),
commands: Vec::new(),
}
}
pub fn get_cell(&self, x: u16, y: u16) -> Option<Cell> {
if x >= self.cols || y >= self.rows {
return None;
}
let idx = (y as usize) * (self.cols as usize) + (x as usize);
self.cells.get(idx).copied()
}
pub fn set_cell(&mut self, x: u16, y: u16, cell: Cell) -> bool {
if x >= self.cols || y >= self.rows {
return false;
}
let idx = (y as usize) * (self.cols as usize) + (x as usize);
if let Some(c) = self.cells.get_mut(idx) {
*c = cell;
true
} else {
false
}
}
pub fn get_line(&self, y: u16) -> Option<String> {
if y >= self.rows {
return None;
}
let start = (y as usize) * (self.cols as usize);
let end = start + (self.cols as usize);
Some(
self.cells[start..end]
.iter()
.map(|c| c.c)
.collect::<String>()
.trim_end()
.to_string(),
)
}
}
#[derive(Clone)]
pub struct PluginContext {
pub config: PluginConfigData,
pub state: Arc<Mutex<PluginSharedState>>,
pub logger_name: String,
pub commands: Arc<Mutex<Vec<RemoteCommand>>>,
}
impl PluginContext {
pub fn new(
config: PluginConfigData,
state: Arc<Mutex<PluginSharedState>>,
logger_name: impl Into<String>,
) -> Self {
Self {
config,
state,
logger_name: logger_name.into(),
commands: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn queue_command(&self, cmd: RemoteCommand) {
self.commands.lock().push(cmd);
}
pub fn get_cell(&self, x: u16, y: u16) -> Option<Cell> {
self.state.lock().get_cell(x, y)
}
pub fn set_cell(&self, x: u16, y: u16, cell: Cell) -> bool {
self.state.lock().set_cell(x, y, cell)
}
pub fn get_line(&self, y: u16) -> Option<String> {
self.state.lock().get_line(y)
}
pub fn get_size(&self) -> (u16, u16) {
let state = self.state.lock();
(state.cols, state.rows)
}
pub fn get_cursor(&self) -> (u16, u16) {
self.state.lock().cursor
}
pub fn get_env(&self, key: &str) -> Option<String> {
self.state.lock().env.get(key).cloned()
}
pub fn set_data(&self, key: impl Into<String>, value: impl Into<String>) {
self.state.lock().data.insert(key.into(), value.into());
}
pub fn get_data(&self, key: &str) -> Option<String> {
self.state.lock().data.get(key).cloned()
}
pub fn log(&self, level: LogLevel, message: &str) {
match level {
LogLevel::Error => log::error!("[{}] {}", self.logger_name, message),
LogLevel::Warn => log::warn!("[{}] {}", self.logger_name, message),
LogLevel::Info => log::info!("[{}] {}", self.logger_name, message),
LogLevel::Debug => log::debug!("[{}] {}", self.logger_name, message),
}
self.queue_command(RemoteCommand::PluginLog {
plugin_name: self.logger_name.clone(),
level,
message: message.to_string(),
});
}
pub fn notify(&self, title: &str, body: &str, level: NotifyLevel) {
self.queue_command(RemoteCommand::PluginNotify {
title: title.to_string(),
body: body.to_string(),
level,
});
}
pub fn notify_info(&self, title: &str, body: &str) {
self.notify(title, body, NotifyLevel::Info);
}
pub fn notify_success(&self, title: &str, body: &str) {
self.notify(title, body, NotifyLevel::Success);
}
pub fn notify_warning(&self, title: &str, body: &str) {
self.notify(title, body, NotifyLevel::Warning);
}
pub fn notify_error(&self, title: &str, body: &str) {
self.notify(title, body, NotifyLevel::Error);
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum NotifyLevel {
Error,
Warning,
Info,
Success,
}
#[derive(Debug, Clone, Default, Deserialize, serde::Serialize)]
pub struct PluginConfigData {
#[serde(flatten)]
pub data: HashMap<String, toml::Value>,
}
impl PluginConfigData {
pub fn get<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Result<T> {
let value = self.data.get(key).ok_or_else(|| {
crate::error::PluginError::ConfigError(format!("Missing key: {}", key))
})?;
T::deserialize(value.clone())
.map_err(|e| crate::error::PluginError::ConfigError(e.to_string()))
}
pub fn get_opt<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Option<T> {
self.data
.get(key)
.and_then(|v| T::deserialize(v.clone()).ok())
}
}