256 lines
7.3 KiB
Rust
256 lines
7.3 KiB
Rust
#![feature(rustc_private, if_let_guard)]
|
|
#![allow(clippy::new_without_default)]
|
|
|
|
use std::{
|
|
cell::RefCell,
|
|
env,
|
|
io::{self, IsTerminal},
|
|
path::{Path, PathBuf},
|
|
rc::Rc,
|
|
};
|
|
|
|
use libterm::TermKey;
|
|
|
|
use error::Error;
|
|
use lysp::{
|
|
error::MachineError,
|
|
vm::{
|
|
Value,
|
|
env::{Environment, EnvironmentAccessHook},
|
|
machine::Machine,
|
|
value::{
|
|
IdentifierValue, StringValue,
|
|
convert::{AnyFunction, TryFromValue},
|
|
},
|
|
},
|
|
};
|
|
|
|
use crate::{config::EditorConfig, script::AsValue, state::State};
|
|
|
|
pub mod buffer;
|
|
pub mod config;
|
|
pub mod error;
|
|
pub mod highlight;
|
|
pub mod keymap;
|
|
pub mod state;
|
|
pub mod text;
|
|
|
|
mod script;
|
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
pub enum TopMode {
|
|
Normal,
|
|
Command,
|
|
}
|
|
|
|
pub struct Editor {
|
|
machine: Machine,
|
|
state: Rc<RefCell<State>>,
|
|
env: Rc<Environment>,
|
|
}
|
|
|
|
impl Editor {
|
|
pub fn new<P: AsRef<Path>>(path: Option<P>) -> Result<Self, Error> {
|
|
#[allow(unused)]
|
|
struct ConfigAccessHook(Rc<RefCell<State>>, Rc<RefCell<EditorConfig>>);
|
|
|
|
impl EnvironmentAccessHook for ConfigAccessHook {
|
|
fn read_variable(&mut self, name: &str) -> Option<Value> {
|
|
let config = self.1.borrow();
|
|
|
|
match name {
|
|
"red/number" => Some((*config.number).into()),
|
|
"red/bottom-margin" => Some((*config.bottom_margin).into()),
|
|
_ => None,
|
|
}
|
|
}
|
|
fn write_variable(
|
|
&mut self,
|
|
name: &IdentifierValue,
|
|
value: &Value,
|
|
) -> Result<bool, MachineError> {
|
|
match name.as_ref() {
|
|
"red/number" => {
|
|
let mut config = self.1.borrow_mut();
|
|
*config.number = value.is_trueish();
|
|
Ok(true)
|
|
}
|
|
"red/bottom-margin" => {
|
|
let value = usize::try_from_value(value)?;
|
|
let mut config = self.1.borrow_mut();
|
|
*config.bottom_margin = value;
|
|
Ok(true)
|
|
}
|
|
_ => Ok(false),
|
|
}
|
|
}
|
|
}
|
|
|
|
let config = Rc::new(RefCell::new(EditorConfig::default()));
|
|
let state = Rc::new(RefCell::new(State::open(path, config.clone())?));
|
|
let machine = Machine::default();
|
|
let env = Rc::new(Environment::default());
|
|
env.set_access_hook(Some(Box::new(ConfigAccessHook(state.clone(), config))));
|
|
|
|
Ok(Self {
|
|
state,
|
|
machine,
|
|
env,
|
|
})
|
|
}
|
|
|
|
pub fn defun<S, F>(&self, name: S, function: F)
|
|
where
|
|
S: Into<IdentifierValue>,
|
|
F: Fn(
|
|
&mut Machine,
|
|
&Rc<Environment>,
|
|
&Rc<RefCell<State>>,
|
|
&[Value],
|
|
) -> Result<Value, Error>
|
|
+ 'static,
|
|
{
|
|
let state = self.state.clone();
|
|
self.env.defun_native(name, "", move |vm, env, args| {
|
|
match function(vm, env, &state, args) {
|
|
Ok(value) => Ok(value),
|
|
Err(error) => Err(MachineError::Custom(error.into())),
|
|
}
|
|
});
|
|
}
|
|
pub fn defmacro<S, F>(&self, name: S, function: F)
|
|
where
|
|
S: Into<IdentifierValue>,
|
|
F: Fn(
|
|
&mut Machine,
|
|
&Rc<Environment>,
|
|
&Rc<RefCell<State>>,
|
|
&[Value],
|
|
) -> Result<Value, Error>
|
|
+ 'static,
|
|
{
|
|
let state = self.state.clone();
|
|
self.env.defmacro_native(name, "", move |vm, env, args| {
|
|
match function(vm, env, &state, args) {
|
|
Ok(value) => Ok(value),
|
|
Err(error) => Err(MachineError::Custom(error.into())),
|
|
}
|
|
});
|
|
}
|
|
|
|
pub fn evaluate_file<P: AsRef<Path>>(&mut self, path: P) {
|
|
let path = path.as_ref();
|
|
if let Err(error) = self.machine.load_file(Default::default(), &self.env, path) {
|
|
log::error!("{}: {}", path.display(), error);
|
|
self.env
|
|
.set_global_value(
|
|
"_red/current-message",
|
|
Value::String(format!("{}: {}", path.display(), error).into()),
|
|
)
|
|
.ok();
|
|
}
|
|
}
|
|
|
|
pub fn evaluate_callback(&mut self, function: &AnyFunction, args: &[Value]) {
|
|
let name = function.name();
|
|
if let Err(error) = function.invoke(&mut self.machine, &self.env, args) {
|
|
log::error!("{name}: {error}");
|
|
self.env
|
|
.set_global_value(
|
|
"_red/current-message",
|
|
Value::String(format!("{name}: {error}").into()),
|
|
)
|
|
.ok();
|
|
}
|
|
}
|
|
|
|
pub fn run(&mut self) -> Result<(), Error> {
|
|
script::setup_env(self);
|
|
|
|
let runtime_directory: PathBuf;
|
|
#[cfg(feature = "runtime")]
|
|
{
|
|
runtime_directory = PathBuf::from("runtime").canonicalize().unwrap();
|
|
}
|
|
#[cfg(any(not(feature = "runtime"), rust_analyzer))]
|
|
{
|
|
runtime_directory = "/usr/share/red/runtime".into();
|
|
}
|
|
|
|
self.env
|
|
.set_global_value(
|
|
"red/runtime-directory",
|
|
StringValue::from(format!("{}", runtime_directory.display())),
|
|
)
|
|
.ok();
|
|
self.evaluate_file(runtime_directory.join("core.lysp"));
|
|
|
|
loop {
|
|
let exited = self.state.borrow().exited();
|
|
|
|
if exited {
|
|
break;
|
|
}
|
|
|
|
// TODO pre-render hook
|
|
let (width, height) = self.state.borrow_mut().display()?;
|
|
|
|
let post_render_hook = self.state.borrow().post_render_hook.clone();
|
|
if let Some(post_render_hook) = post_render_hook {
|
|
self.evaluate_callback(&post_render_hook, &[width.into(), height.into()]);
|
|
}
|
|
|
|
self.state.borrow_mut().finish_display()?;
|
|
|
|
let key = self.state.borrow_mut().wait_for_events()?;
|
|
|
|
if let TermKey::Char('Q') = key {
|
|
break;
|
|
}
|
|
|
|
let (key_hook, top_mode, buffer_mode) = {
|
|
let state = self.state.borrow();
|
|
let (top_mode, buffer_mode) = state.mode();
|
|
let key_hook = state.key_hook.clone();
|
|
|
|
(key_hook, top_mode, buffer_mode)
|
|
};
|
|
|
|
if let Some(key_hook) = key_hook {
|
|
let key = key.as_value();
|
|
let top_mode = top_mode.as_value();
|
|
let buffer_mode = buffer_mode.as_value();
|
|
self.evaluate_callback(
|
|
&key_hook,
|
|
&[top_mode.into(), buffer_mode.into(), key.into()],
|
|
);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
logsink::setup_logging(false);
|
|
|
|
let args = env::args().collect::<Vec<_>>();
|
|
if args.len() > 2 {
|
|
eprintln!("Usage: red [FILE]");
|
|
return;
|
|
}
|
|
|
|
if !io::stdin().is_terminal() {
|
|
eprintln!("Not a tty");
|
|
return;
|
|
}
|
|
|
|
let path = args.get(1);
|
|
let mut editor = Editor::new(path).unwrap();
|
|
let result = editor.run();
|
|
editor.state.borrow_mut().cleanup();
|
|
|
|
if let Err(error) = result {
|
|
eprintln!("Error: {error}");
|
|
}
|
|
}
|