334 lines
11 KiB
Rust
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);
|
|
}
|