Files
yggdrasil/userspace/tools/red/src/script/mod.rs
T

334 lines
11 KiB
Rust

use crate::{
Editor,
buffer::SetMode,
highlight::{SyntaxRule, SyntaxStyle},
keymap::KeySeq,
};
pub mod convert;
pub use convert::{AsValue, FromValue, Movement};
use libterm::Color;
use lysp::{
error::MachineError,
vm::{
Value, prelude,
value::{
StringValue,
convert::{AnyFunction, TryFromValue},
},
},
};
fn editor_api(editor: &mut Editor) {
editor.defun("red/bind-normal-hook", |_, _, state, args| {
let [seq, handler] = args else {
return Err(MachineError::InvalidArgumentCount.into());
};
let seq = KeySeq::from_value(seq)?;
let handler = AnyFunction::try_from_value(handler)?;
let mut state = state.borrow_mut();
state.normal_map.set(seq, handler);
Ok(Value::Nil)
});
editor.defun("red/bind-insert-hook", |_, _, state, args| {
let [seq, handler] = args else {
return Err(MachineError::InvalidArgumentCount.into());
};
let seq = KeySeq::from_value(seq)?;
let handler = AnyFunction::try_from_value(handler)?;
let mut state = state.borrow_mut();
state.insert_map.set(seq, handler);
Ok(Value::Nil)
});
editor.defun("red/bind-command-hook", |_, _, state, args| {
let [hook] = args else {
return Err(MachineError::InvalidArgumentCount.into());
};
let hook = AnyFunction::try_from_value(hook)?;
state.borrow_mut().command_hook = Some(hook);
Ok(Value::Nil)
});
editor.defun("red/bind-post-render-hook", |_, _, state, args| {
let [hook] = args else {
return Err(MachineError::InvalidArgumentCount.into());
};
let hook = AnyFunction::try_from_value(hook)?;
state.borrow_mut().post_render_hook = Some(hook);
Ok(Value::Nil)
});
editor.defun("red/quit", |_, _, state, args| {
let force = match args {
[] => false,
[arg, ..] => arg.is_trueish(),
};
state.borrow_mut().exit(force);
Ok(Value::Nil)
});
editor.defun("red/message", |_, _, state, args| {
let mut message = String::new();
for (i, arg) in args.iter().enumerate() {
if i != 0 {
message.push(' ');
}
message.push_str(&format!("{arg}"));
}
state.borrow_mut().set_message(message);
Ok(Value::Nil)
});
}
fn syntax_api(editor: &mut Editor) {
editor.defun("red/syntax/define-regex-rule", |_, _, state, args| {
// ( <SYNTAX> <STATE> <PATTERN> <CATEGORY> &optional <NEXT-STATE> )
let (filetype, entry_state, pattern, category, next_state) = match args {
[a, b, c, d] => (a, b, c, d, &Value::Nil),
[a, b, c, d, e] => (a, b, c, d, e),
_ => return Err(MachineError::InvalidArgumentCount.into()),
};
let filetype = StringValue::try_from_value(filetype)?;
let entry_state = usize::try_from_value(entry_state)? as u32;
let pattern = StringValue::try_from_value(pattern)?;
let category = StringValue::try_from_value(category)?;
let next_state = if next_state.is_nil() {
None
} else {
Some(usize::try_from_value(next_state)? as u32)
};
let rule = match SyntaxRule::regex(&pattern, next_state, category.as_rc_str().clone()) {
Some(rule) => rule,
None => return Err(MachineError::InstructionFetch.into()),
};
let mut state = state.borrow_mut();
state
.highlight
.define_rule(filetype.as_rc_str().clone(), entry_state, rule);
Ok(Value::Nil)
});
editor.defun("red/syntax/define-keyword-rule", |_, _, state, args| {
// ( <SYNTAX> <STATE> <PATTERN> <CATEGORY> &optional <NEXT-STATE> )
let (filetype, entry_state, pattern, category, next_state) = match args {
[a, b, c, d] => (a, b, c, d, &Value::Nil),
[a, b, c, d, e] => (a, b, c, d, e),
_ => return Err(MachineError::InvalidArgumentCount.into()),
};
let filetype = StringValue::try_from_value(filetype)?;
let entry_state = usize::try_from_value(entry_state)? as u32;
let pattern = StringValue::try_from_value(pattern)?;
let category = StringValue::try_from_value(category)?;
let next_state = if next_state.is_nil() {
None
} else {
Some(usize::try_from_value(next_state)? as u32)
};
let rule = SyntaxRule::keyword(
pattern.as_rc_str().clone(),
next_state,
category.as_rc_str().clone(),
);
let mut state = state.borrow_mut();
state
.highlight
.define_rule(filetype.as_rc_str().clone(), entry_state, rule);
Ok(Value::Nil)
});
editor.defun("red/syntax/define-category-style", |_, _, state, args| {
// ( <SYNTAX> <CATEGORY> <FOREGROUND> <BACKGROUND> <BOLD> )
let (filetype, category, foreground, background, bold) = match args {
[a, b, c] => (a, b, c, &Value::Nil, &Value::Nil),
[a, b, c, d] => (a, b, c, d, &Value::Nil),
[a, b, c, d, e] => (a, b, c, d, e),
_ => return Err(MachineError::InvalidArgumentCount.into()),
};
let filetype = StringValue::try_from_value(filetype)?;
let category = StringValue::try_from_value(category)?;
let foreground = if foreground.is_nil() {
None
} else {
Some(Color::from_value(foreground)?)
};
let background = if background.is_nil() {
None
} else {
Some(Color::from_value(background)?)
};
let bold = if bold.is_nil() {
None
} else {
Some(bold.is_trueish())
};
let style = SyntaxStyle {
foreground,
background,
bold,
};
let mut state = state.borrow_mut();
state.highlight.define_category(
filetype.as_rc_str().clone(),
category.as_rc_str().clone(),
style,
);
Ok(Value::Nil)
});
editor.defun("red/syntax/reset", |_, _, state, _| {
let mut state = state.borrow_mut();
state.buffer_mut().reset_highlight();
Ok(Value::Nil)
});
}
fn buffer_api(editor: &mut Editor) {
editor.defun("red/buffer/path", |_, _, state, _| {
let state = state.borrow();
let path = state.buffer().path();
if let Some(path) = path {
Ok(Value::String(format!("{}", path.display()).into()))
} else {
Ok(Value::Nil)
}
});
editor.defun("red/buffer/mode", |_, _, state, _| {
let state = state.borrow();
let (top_mode, buffer_mode) = state.mode();
let top_mode = top_mode.as_value();
let buffer_mode = buffer_mode.as_value();
Ok(Value::list_or_nil([top_mode.into(), buffer_mode.into()]))
});
editor.defun("red/buffer/set-mode", |_, _, state, args| {
let [mode] = args else {
return Err(MachineError::InvalidArgumentCount.into());
};
let target_mode = SetMode::from_value(mode)?;
let mut state = state.borrow_mut();
state.set_mode(target_mode);
Ok(Value::Nil)
});
editor.defun("red/buffer/write", |_, _, state, args| {
let path = match args {
[] => None,
[path] => Some(StringValue::try_from_value(path)?),
_ => return Err(MachineError::InvalidArgumentCount.into()),
};
let mut state = state.borrow_mut();
state.write_buffer(path.as_deref())?;
Ok(Value::Nil)
});
editor.defun("red/buffer/open", |_, _, state, args| {
let (path, force) = match args {
[] => return Ok(Value::Nil),
[path] => (StringValue::try_from_value(path)?, false),
[path, force] => (StringValue::try_from_value(path)?, force.is_trueish()),
_ => return Err(MachineError::InvalidArgumentCount.into()),
};
let mut state = state.borrow_mut();
state.open_buffer(&*path, force)?;
Ok(Value::Nil)
});
editor.defun("red/buffer/move", |_, _, state, args| {
let [movement] = args else {
return Err(MachineError::InvalidArgumentCount.into());
};
let movement = Movement::from_value(movement)?;
let mut state = state.borrow_mut();
state.move_cursor(movement);
Ok(Value::Nil)
});
editor.defun("red/buffer/insert-line-before", |_, _, state, _| {
let mut state = state.borrow_mut();
state.buffer_mut().newline_before();
Ok(Value::Nil)
});
editor.defun("red/buffer/insert-line-after", |_, _, state, args| {
let break_line = match args {
[] => false,
[arg, ..] => arg.is_trueish(),
};
let mut state = state.borrow_mut();
state.buffer_mut().newline_after(break_line);
Ok(Value::Nil)
});
editor.defun("red/buffer/kill-line", |_, _, state, _| {
let mut state = state.borrow_mut();
state.kill_current_line();
Ok(Value::Nil)
});
editor.defun("red/buffer/erase-backward", |_, _, state, _| {
let mut state = state.borrow_mut();
state.erase_backward();
Ok(Value::Nil)
});
editor.defun("red/buffer/term-cursor", |_, _, state, _| {
let state = state.borrow();
match state.buffer_terminal_cursor() {
Some((x, y)) => Ok(Value::list_or_nil([x.into(), y.into()])),
None => Ok(Value::Nil),
}
});
}
fn term_api(editor: &mut Editor) {
editor.defun("term/fg-color", |_, _, state, args| {
let [color] = args else {
return Err(MachineError::InvalidArgumentCount.into());
};
let mut state = state.borrow_mut();
let color = Color::from_value(color)?;
state.term.set_foreground(color).ok();
Ok(Value::Nil)
});
editor.defun("term/bg-color", |_, _, state, args| {
let [color] = args else {
return Err(MachineError::InvalidArgumentCount.into());
};
let mut state = state.borrow_mut();
let color = Color::from_value(color)?;
state.term.set_background(color).ok();
Ok(Value::Nil)
});
editor.defun("term/write", |_, _, state, args| {
use std::fmt::Write;
let [message] = args else {
return Err(MachineError::InvalidArgumentCount.into());
};
let mut state = state.borrow_mut();
write!(state.term, "{message}").ok();
Ok(Value::Nil)
});
editor.defun("term/set-cursor", |_, _, state, args| {
let [row, column] = args else {
return Err(MachineError::InvalidArgumentCount.into());
};
let row = usize::try_from_value(row)?;
let column = usize::try_from_value(column)?;
let mut state = state.borrow_mut();
state.term.set_cursor_position(row, column).ok();
Ok(Value::Nil)
});
}
pub fn setup_env(editor: &mut Editor) {
prelude::load(&editor.env);
editor_api(editor);
buffer_api(editor);
term_api(editor);
syntax_api(editor);
}