use anyhow::{anyhow, Result};
use easy_error::Error as BundError;
use rust_dynamic::types::STRING;
use rust_dynamic::value::Value;
use rust_multistackvm::multistackvm::VM;
use std::cell::RefCell;
use std::collections::HashMap;
use super::helpers::{active_store, pull, push, require_depth, value_to_string};
use crate::pane::output::{kinds, Lifetime, Message, OutputStore, Severity};
thread_local! {
static PRINT_BUFFER: RefCell<String> = const { RefCell::new(String::new()) };
static OUTPUT_LINE: RefCell<String> = const { RefCell::new(String::new()) };
}
pub fn register(vm: &mut VM) -> Result<()> {
vm.register_inline("print".to_string(), ink_print)
.map_err(|e| anyhow!("register print: {e}"))?;
vm.register_inline("println".to_string(), ink_println)
.map_err(|e| anyhow!("register println: {e}"))?;
let words: &[(&str, fn(&mut VM) -> std::result::Result<&mut VM, BundError>)] = &[
("ink.io.print", w_io_print),
("ink.io.log", w_io_log),
("ink.io.notify", w_io_notify),
("ink.io.message.list", w_io_msg_list),
("ink.io.message.count", w_io_msg_count),
("ink.io.message.dismiss", w_io_msg_dismiss),
("ink.io.message.pin", w_io_msg_pin),
("ink.io.message.unpin", w_io_msg_unpin),
];
for (name, f) in words {
vm.register_inline(name.to_string(), *f)
.map_err(|e| anyhow!("register {name}: {e}"))?;
}
Ok(())
}
fn to_bund_err(e: anyhow::Error) -> BundError {
easy_error::err_msg(format!("{e}"))
}
fn output_store(tag: &str) -> Result<OutputStore> {
if let Some(os) = crate::pane::output::active() {
return Ok(os);
}
let store = active_store(tag)?;
crate::pane::output::install(store.project_root()).map_err(|e| anyhow!("{tag}: {e}"))
}
fn w_io_print(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_io_print(vm).map_err(to_bund_err)
}
fn do_io_print(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.io.print";
require_depth(vm, 1, tag)?;
let text = value_to_string(pull(vm, tag)?, "text", tag)?;
let os = output_store(tag)?;
let msg = Message::new(
kinds::BUND_PRINT,
Severity::Info,
Lifetime::Session(100),
serde_json::json!({ "text": text }),
);
os.emit(&msg).map_err(|e| anyhow!("{tag}: {e}"))?;
Ok(vm)
}
fn w_io_log(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_io_log(vm).map_err(to_bund_err)
}
fn do_io_log(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.io.log";
require_depth(vm, 2, tag)?;
let level = value_to_string(pull(vm, tag)?, "level", tag)?;
let text = value_to_string(pull(vm, tag)?, "text", tag)?;
let severity = match level.as_str() {
"warn" | "warning" => Severity::Warning,
"error" => Severity::Contradiction,
_ => Severity::Info,
};
let os = output_store(tag)?;
let msg = Message::new(
kinds::BUND_LOG,
severity,
Lifetime::Session(200),
serde_json::json!({ "text": text, "level": level }),
);
os.emit(&msg).map_err(|e| anyhow!("{tag}: {e}"))?;
Ok(vm)
}
fn w_io_notify(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_io_notify(vm).map_err(to_bund_err)
}
fn do_io_notify(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.io.notify";
require_depth(vm, 2, tag)?;
let metadata_v = pull(vm, tag)?;
let kind = value_to_string(pull(vm, tag)?, "kind", tag)?;
let metadata = crate::scripting::value_to_json(&metadata_v);
let os = output_store(tag)?;
let msg = Message::new(kind, Severity::Info, Lifetime::UntilActedOn, metadata);
let id = os.emit(&msg).map_err(|e| anyhow!("{tag}: {e}"))?;
push(vm, Value::from_string(id.to_string()));
Ok(vm)
}
fn message_dict(m: &Message) -> Value {
let mut d: HashMap<String, Value> = HashMap::new();
d.insert("id".into(), Value::from_string(m.id.to_string()));
d.insert("kind".into(), Value::from_string(m.kind.clone()));
d.insert("severity".into(), Value::from_string(m.severity.as_str().to_string()));
let text =
m.metadata.get("text").and_then(|v| v.as_str()).unwrap_or("").to_string();
d.insert("text".into(), Value::from_string(text));
Value::from_dict(d)
}
fn w_io_msg_list(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_io_msg_list(vm).map_err(to_bund_err)
}
fn do_io_msg_list(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.io.message.list";
require_depth(vm, 1, tag)?;
let kind = value_to_string(pull(vm, tag)?, "kind", tag)?;
let os = output_store(tag)?;
let msgs = if kind.trim().is_empty() {
os.active()
} else {
os.by_kind(&kind)
}
.map_err(|e| anyhow!("{tag}: {e}"))?;
push(vm, Value::from_list(msgs.iter().map(message_dict).collect()));
Ok(vm)
}
fn w_io_msg_count(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_io_msg_count(vm).map_err(to_bund_err)
}
fn do_io_msg_count(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.io.message.count";
require_depth(vm, 1, tag)?;
let kind = value_to_string(pull(vm, tag)?, "kind", tag)?;
let os = output_store(tag)?;
let k = (!kind.trim().is_empty()).then_some(kind.as_str());
let n = os.count_active(k).map_err(|e| anyhow!("{tag}: {e}"))?;
push(vm, Value::from_int(n as i64));
Ok(vm)
}
fn pull_id(vm: &mut VM, tag: &str) -> Result<uuid::Uuid> {
let s = value_to_string(pull(vm, tag)?, "id", tag)?;
uuid::Uuid::parse_str(&s).map_err(|e| anyhow!("{tag}: bad id `{s}`: {e}"))
}
fn w_io_msg_dismiss(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_io_msg_dismiss(vm).map_err(to_bund_err)
}
fn do_io_msg_dismiss(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.io.message.dismiss";
require_depth(vm, 1, tag)?;
let id = pull_id(vm, tag)?;
output_store(tag)?.dismiss(id).map_err(|e| anyhow!("{tag}: {e}"))?;
Ok(vm)
}
fn w_io_msg_pin(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_io_msg_pin(vm).map_err(to_bund_err)
}
fn do_io_msg_pin(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.io.message.pin";
require_depth(vm, 1, tag)?;
let id = pull_id(vm, tag)?;
output_store(tag)?.set_pinned(id, true).map_err(|e| anyhow!("{tag}: {e}"))?;
Ok(vm)
}
fn w_io_msg_unpin(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_io_msg_unpin(vm).map_err(to_bund_err)
}
fn do_io_msg_unpin(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.io.message.unpin";
require_depth(vm, 1, tag)?;
let id = pull_id(vm, tag)?;
output_store(tag)?.set_pinned(id, false).map_err(|e| anyhow!("{tag}: {e}"))?;
Ok(vm)
}
pub fn drain_print_buffer() -> String {
PRINT_BUFFER.with(|b| std::mem::take(&mut *b.borrow_mut()))
}
fn ink_print(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
capture(vm, false)
}
fn ink_println(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
capture(vm, true)
}
fn capture(vm: &mut VM, newline: bool) -> std::result::Result<&mut VM, BundError> {
let value = match vm.stack.pull() {
Some(v) => v,
None => {
return Err(easy_error::err_msg(
"PRINT returns: NO DATA",
));
}
};
let str_value = value
.conv(STRING)
.map_err(|e| easy_error::err_msg(format!("PRINT conv: {e}")))?;
let text = str_value
.cast_string()
.map_err(|e| easy_error::err_msg(format!("PRINT cast: {e}")))?;
let routed_to_pane = crate::scripting::with_active_app(|app| {
app.append_to_bund_pane(&text, newline)
})
.unwrap_or(false);
if !routed_to_pane {
PRINT_BUFFER.with(|b| {
let mut g = b.borrow_mut();
g.push_str(&text);
if newline {
g.push('\n');
}
});
}
mirror_bare_print_to_output(&text, newline);
Ok(vm)
}
fn mirror_bare_print_to_output(text: &str, newline: bool) {
if crate::pane::output::active().is_none() {
return;
}
OUTPUT_LINE.with(|b| {
let mut buf = b.borrow_mut();
buf.push_str(text);
if newline {
buf.push('\n');
}
while let Some(nl) = buf.find('\n') {
let line: String = buf.drain(..=nl).collect();
let line = line.trim_end_matches(['\n', '\r']);
if !line.trim().is_empty() {
let msg = Message::new(
kinds::BUND_PRINT,
Severity::Info,
Lifetime::Session(100),
serde_json::json!({ "text": line }),
);
crate::pane::output::emit(&msg);
}
}
});
}