diff --git a/userspace/Cargo.lock b/userspace/Cargo.lock index 3d0f2973..a2e0f889 100644 --- a/userspace/Cargo.lock +++ b/userspace/Cargo.lock @@ -644,31 +644,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" -dependencies = [ - "bitflags 2.11.0", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - [[package]] name = "crypt" version = "0.1.0" @@ -1057,15 +1032,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "version_check", -] - [[package]] name = "fastrand" version = "2.3.0" @@ -1349,17 +1315,6 @@ dependencies = [ "digest", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "http" version = "1.3.1" @@ -1812,12 +1767,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "md2txt" version = "0.1.0" @@ -1859,18 +1808,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - [[package]] name = "ndk" version = "0.9.0" @@ -2029,15 +1966,6 @@ dependencies = [ "syn", ] -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -2310,29 +2238,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.13", - "smallvec", - "windows-targets 0.52.6", -] - [[package]] name = "pci-ids" version = "0.2.5" @@ -2764,10 +2669,11 @@ name = "red" version = "0.1.0" dependencies = [ "cross", - "crossterm", - "libc", "libterm", - "syslog", + "log", + "logsink", + "lysp", + "regex", "thiserror 1.0.69", "unicode-width 0.1.14", ] @@ -2792,9 +2698,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -2804,9 +2710,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3119,36 +3025,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" -dependencies = [ - "libc", -] - [[package]] name = "signature" version = "2.2.0" @@ -3365,19 +3241,6 @@ dependencies = [ "syn", ] -[[package]] -name = "syslog" -version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc7e95b5b795122fafe6519e27629b5ab4232c73ebb2428f568e82b1a457ad3" -dependencies = [ - "error-chain", - "hostname", - "libc", - "log", - "time", -] - [[package]] name = "sysutils" version = "0.1.0" @@ -3481,14 +3344,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", - "itoa", - "libc", "num-conv", - "num_threads", "powerfmt", - "serde", "time-core", - "time-macros", ] [[package]] @@ -3497,16 +3355,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" -[[package]] -name = "time-macros" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tiny-skia" version = "0.11.4" @@ -3961,22 +3809,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" version = "0.1.9" @@ -3986,12 +3818,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.45.0" @@ -4001,15 +3827,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" diff --git a/userspace/lib/lysp/examples/import.lysp b/userspace/lib/lysp/examples/import.lysp index c551095b..5b5e1c81 100644 --- a/userspace/lib/lysp/examples/import.lysp +++ b/userspace/lib/lysp/examples/import.lysp @@ -1 +1 @@ -(import "examples/io.lysp") +(import "io.lysp") diff --git a/userspace/lib/lysp/src/compile/block.rs b/userspace/lib/lysp/src/compile/block.rs index 6fcde9fe..6cfbaa50 100644 --- a/userspace/lib/lysp/src/compile/block.rs +++ b/userspace/lib/lysp/src/compile/block.rs @@ -1,6 +1,7 @@ use std::{ collections::HashMap, ops::{Deref, DerefMut}, + path::Path, rc::Rc, }; @@ -31,6 +32,7 @@ pub struct FunctionBlock { identifier: Option, docstring: Option, signature: FunctionSignature, + script_path: Option>, // Data pub(crate) constants: Vec, @@ -81,6 +83,7 @@ pub struct CompileContext { pub function_blocks: Vec, pub(crate) current: usize, pub(crate) options: CompileOptions, + script_path: Option>, } pub trait Compile { @@ -88,11 +91,16 @@ pub trait Compile { } impl CompileContext { - pub fn new(options: CompileOptions, root_name: Option) -> Self { + pub fn new( + options: CompileOptions, + root_name: Option, + script_path: Option>, + ) -> Self { Self { - function_blocks: vec![FunctionBlock::root(root_name)], + function_blocks: vec![FunctionBlock::root(root_name, script_path.clone())], current: 0, options, + script_path, } } @@ -100,8 +108,9 @@ impl CompileContext { options: CompileOptions, chunk_name: Option, value: &Value, + script_path: Option>, ) -> Result, CompileError> { - let mut cx = Self::new(options, chunk_name); + let mut cx = Self::new(options, chunk_name, script_path); let expression = Expression::parse(value).map_err(CompileError::Parse)?; let value = expression.compile(&mut cx)?; cx.compile_return_value(value)?; @@ -141,7 +150,13 @@ impl CompileContext { if self.options.trace_compile { eprintln!("COMPILE: push_lambda_context({identifier:?})"); } - let block = FunctionBlock::new(Some(self.current), identifier, docstring, signature); + let block = FunctionBlock::new( + Some(self.current), + identifier, + docstring, + signature, + self.script_path.clone(), + ); let index = self.function_blocks.len(); self.function_blocks.push(block); self.current = index; @@ -384,6 +399,7 @@ impl FunctionBlock { identifier: Option, docstring: Option, signature: &FunctionSignature, + script_path: Option>, ) -> Self { let mut block = Self { parent, @@ -397,6 +413,7 @@ impl FunctionBlock { instructions: vec![], labels: vec![], loop_stack: vec![], + script_path, }; for required in signature.required_arguments.iter() { block @@ -416,7 +433,7 @@ impl FunctionBlock { block } - fn root(identifier: Option) -> Self { + fn root(identifier: Option, script_path: Option>) -> Self { Self::new( None, identifier, @@ -426,6 +443,7 @@ impl FunctionBlock { optional_arguments: vec![], rest_argument: None, }, + script_path, ) } @@ -531,6 +549,7 @@ impl FunctionBlock { required_count: self.signature.required_arguments.len(), optional_count: self.signature.optional_arguments.len(), has_rest: self.signature.rest_argument.is_some(), + script: self.script_path.clone(), })) } @@ -631,7 +650,7 @@ impl FunctionBlock { pub fn test_compile( expression: &Expression, ) -> Result<(CompileContext, CompileValue), CompileError> { - let mut cx = CompileContext::new(Default::default(), None); + let mut cx = CompileContext::new(Default::default(), None, None); let value = expression.compile(&mut cx)?; Ok((cx, value)) } diff --git a/userspace/lib/lysp/src/main.rs b/userspace/lib/lysp/src/main.rs index 8e8c3881..dea5703f 100644 --- a/userspace/lib/lysp/src/main.rs +++ b/userspace/lib/lysp/src/main.rs @@ -180,7 +180,7 @@ fn run_module>( let path = path.as_ref(); let name = format!("{}", path.display()); let reader = BufReader::new(File::open(path)?); - let module_reader = ModuleReader::new(reader, vm.trace_macros); + let module_reader = ModuleReader::new(reader, path, vm.trace_macros); let function = match module_reader.compile(Some(name.into()), compile_options, env) { Ok(function) => function, Err(error) => return Err(handle_module_error(error)), diff --git a/userspace/lib/lysp/src/read.rs b/userspace/lib/lysp/src/read.rs index 903f6809..a8875e23 100644 --- a/userspace/lib/lysp/src/read.rs +++ b/userspace/lib/lysp/src/read.rs @@ -1,6 +1,7 @@ use std::{ borrow::Cow, io::{BufRead, Write, stdin, stdout}, + path::PathBuf, rc::Rc, }; @@ -39,6 +40,7 @@ pub struct FileReader { pub struct ModuleReader { reader: FileReader, + path: PathBuf, macro_machine: Machine, } @@ -93,11 +95,12 @@ impl Reader for FileReader { } impl ModuleReader { - pub fn new(reader: R, trace_macros: bool) -> Self { + pub fn new>(reader: R, path: P, trace_macros: bool) -> Self { let mut macro_machine = Machine::default(); macro_machine.trace_macros = trace_macros; Self { reader: FileReader::new(reader), + path: path.into(), macro_machine, } } @@ -130,7 +133,8 @@ impl ModuleReader { options: &CompileOptions, env: &Rc, ) -> Result, Either>> { - let mut cx = CompileContext::new(options.clone(), module_name); + let mut cx = + CompileContext::new(options.clone(), module_name, Some(self.path.clone().into())); let mut body = FunctionBody { head: vec![], tail: Rc::new(Expression::Nil), diff --git a/userspace/lib/lysp/src/vm/machine.rs b/userspace/lib/lysp/src/vm/machine.rs index 209b5c21..0c777488 100644 --- a/userspace/lib/lysp/src/vm/machine.rs +++ b/userspace/lib/lysp/src/vm/machine.rs @@ -26,10 +26,11 @@ use crate::{ }, }; -struct CallFrame { - closure: ClosureValue, - ip: usize, - base_pointer: usize, +#[derive(Debug)] +pub struct CallFrame { + pub closure: ClosureValue, + pub ip: usize, + pub base_pointer: usize, } pub struct Machine { @@ -95,6 +96,10 @@ impl Machine { .ok_or(MachineError::DataStackUnderflow) } + pub fn read_call_stack(&self, depth: usize) -> Option<&CallFrame> { + self.call_stack.nth(depth) + } + pub fn current_location(&self) -> Option { self.call_stack.head().map(|frame| MachineErrorLocation { function: frame.closure.function.clone(), @@ -691,9 +696,10 @@ impl Machine { value: Value, ) -> Result { let value_expanded = self.macro_expand(env, &value)?; - let function = CompileContext::compile_value(compile_options, chunk_name, &value_expanded) - .map_err(MachineError::Compile) - .map_err(MachineErrorAt::at_unknown)?; + let function = + CompileContext::compile_value(compile_options, chunk_name, &value_expanded, None) + .map_err(MachineError::Compile) + .map_err(MachineErrorAt::at_unknown)?; let closure = ClosureValue { function, @@ -718,7 +724,7 @@ impl Machine { .map_err(MachineError::Read) .map_err(MachineErrorAt::at_unknown)?, ); - let module_reader = ModuleReader::new(reader, self.trace_macros); + let module_reader = ModuleReader::new(reader, path, self.trace_macros); let function = match module_reader.compile(Some(name.into()), &compile_options, env) { Ok(function) => function, Err(error) => todo!("Handle error: {error:?}"), @@ -799,6 +805,7 @@ mod tests { required_count: 0, optional_count: 0, has_rest: false, + script: None, }), }; let mut machine = Machine::default(); diff --git a/userspace/lib/lysp/src/vm/prelude/debug.rs b/userspace/lib/lysp/src/vm/prelude/debug.rs index 2f12798c..0449c310 100644 --- a/userspace/lib/lysp/src/vm/prelude/debug.rs +++ b/userspace/lib/lysp/src/vm/prelude/debug.rs @@ -1,4 +1,7 @@ -use std::rc::Rc; +use std::{ + path::{Path, PathBuf}, + rc::Rc, +}; use crate::{ error::MachineError, @@ -18,11 +21,31 @@ pub fn load(env: &Rc) { return Err(MachineError::InvalidArgumentCount); } - for arg in args { - let path = StringValue::try_from_value(arg)?; + let caller = vm + .read_call_stack(0) + .ok_or(MachineError::CallStackUnderflow)?; + let caller_directory = caller + .closure + .function + .script + .as_ref() + .and_then(|path| path.parent()) + .map(Path::to_owned) + .unwrap_or_else(|| PathBuf::from(".")); - vm.load_file(Default::default(), env, &*path) - .map_err(|error| MachineError::LoadError(path, error.into()))?; + for arg in args { + let path_str = StringValue::try_from_value(arg)?; + let mut path = PathBuf::from(&*path_str); + + if path.is_relative() { + let in_caller_directory = caller_directory.join(&path); + if in_caller_directory.exists() { + path = in_caller_directory; + } + } + + vm.load_file(Default::default(), env, &path) + .map_err(|error| MachineError::LoadError(path_str, error.into()))?; } Ok(Value::Nil) diff --git a/userspace/lib/lysp/src/vm/stack.rs b/userspace/lib/lysp/src/vm/stack.rs index fa58b4b8..bb990fd5 100644 --- a/userspace/lib/lysp/src/vm/stack.rs +++ b/userspace/lib/lysp/src/vm/stack.rs @@ -55,6 +55,14 @@ impl Stack { } } + pub fn nth(&self, n: usize) -> Option<&T> { + if n >= self.pointer { + None + } else { + Some(unsafe { self.data[self.pointer - n - 1].assume_init_ref() }) + } + } + pub fn get(&self, index: usize) -> Option<&T> { if index < self.pointer { Some(unsafe { self.data[index].assume_init_ref() }) diff --git a/userspace/lib/lysp/src/vm/value/function.rs b/userspace/lib/lysp/src/vm/value/function.rs index 3a1b9518..fab4293a 100644 --- a/userspace/lib/lysp/src/vm/value/function.rs +++ b/userspace/lib/lysp/src/vm/value/function.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{fmt, path::Path, rc::Rc}; use crate::{ compile::UpvalueDef, @@ -16,6 +16,7 @@ pub struct BytecodeFunction { pub instructions: Box<[u8]>, pub constants: Box<[Value]>, pub upvalues: Box<[UpvalueDef]>, + pub script: Option>, pub required_count: usize, pub optional_count: usize, @@ -51,6 +52,10 @@ impl BytecodeFunction { } } + pub fn script_path(&self) -> Option<&Path> { + self.script.as_deref() + } + pub fn min_arity(&self) -> usize { self.required_count } diff --git a/userspace/tools/red/Cargo.lock b/userspace/tools/red/Cargo.lock index aaff7024..f88e7e78 100644 --- a/userspace/tools/red/Cargo.lock +++ b/userspace/tools/red/Cargo.lock @@ -1,24 +1,105 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "autocfg" -version = "1.1.0" +name = "abi-generator" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "thiserror 1.0.69", +] + +[[package]] +name = "abi-lib" +version = "0.1.0" + +[[package]] +name = "abi-serde" +version = "0.1.0" + +[[package]] +name = "aho-corasick" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] -name = "bitflags" -version = "2.4.1" +name = "cc" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] [[package]] name = "cfg-if" @@ -27,59 +108,186 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "crossterm" -version = "0.27.0" +name = "clap" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ - "bitflags 2.4.1", - "crossterm_winapi", + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "cross" +version = "0.1.0" +dependencies = [ + "bitflags", "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", + "runtime", + "tempfile", + "yggdrasil-rt", ] [[package]] -name = "crossterm_winapi" -version = "0.9.1" +name = "dirs" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "winapi", + "dirs-sys", ] [[package]] -name = "deranged" -version = "0.3.9" +name = "dirs-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "version_check", -] - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", - "match_cfg", - "winapi", + "option-ext", + "redox_users", + "windows-sys", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" version = "1.0.9" @@ -87,21 +295,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] -name = "libc" -version = "0.2.150" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] -name = "lock_api" -version = "0.4.11" +name = "libc" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "autocfg", - "scopeguard", + "libc", ] +[[package]] +name = "libterm" +version = "0.1.0" +dependencies = [ + "cross", + "libc", + "thiserror 1.0.69", + "yggdrasil-rt", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "log" version = "0.4.20" @@ -109,117 +338,180 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] -name = "match_cfg" +name = "lysp" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "mio" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", + "clap", + "dirs", + "nom", + "thiserror 2.0.18", ] [[package]] -name = "num_threads" -version = "0.1.6" +name = "memchr" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ - "libc", + "memchr", ] [[package]] -name = "parking_lot" -version = "0.12.1" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "parking_lot_core" -version = "0.9.9" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "powerfmt" +name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "red" version = "0.1.0" dependencies = [ - "crossterm", - "libc", - "syslog", + "cross", + "libterm", + "lysp", + "regex", + "thiserror 2.0.18", + "unicode-width", ] [[package]] -name = "redox_syscall" -version = "0.4.1" +name = "redox_users" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "bitflags 1.3.2", + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "regex" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "runtime" +version = "0.1.0" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -227,46 +519,35 @@ dependencies = [ ] [[package]] -name = "signal-hook" -version = "0.3.17" +name = "serde_json" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ - "libc", - "signal-hook-registry", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", ] [[package]] -name = "signal-hook-mio" -version = "0.2.3" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "signal-hook-registry" -version = "1.4.1" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "smallvec" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.39" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -274,47 +555,56 @@ dependencies = [ ] [[package]] -name = "syslog" -version = "6.1.0" +name = "tempfile" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7434e95bcccce1215d30f4bf84fe8c00e8de1b9be4fb736d747ca53d36e7f96f" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "error-chain", - "hostname", - "libc", - "log", - "time", + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys", ] [[package]] -name = "time" -version = "0.3.30" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "deranged", - "itoa", - "libc", - "num_threads", - "powerfmt", - "serde", - "time-core", - "time-macros", + "thiserror-impl 1.0.69", ] [[package]] -name = "time-core" -version = "0.1.2" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] [[package]] -name = "time-macros" -version = "0.2.15" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "time-core", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -324,101 +614,214 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "version_check" -version = "0.9.4" +name = "unicode-width" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "winapi" -version = "0.3.9" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "wit-bindgen 0.57.1", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "wit-bindgen-rust-macro", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "wit-bindgen-core" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "wit-bindgen-rust" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "wit-bindgen-rust-macro" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "wit-component" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "yggdrasil-abi" +version = "0.1.0" +dependencies = [ + "abi-generator", + "abi-lib", + "abi-serde", + "prettyplease", +] + +[[package]] +name = "yggdrasil-rt" +version = "0.1.0" +dependencies = [ + "abi-generator", + "abi-lib", + "abi-serde", + "cc", + "prettyplease", + "yggdrasil-abi", +] + +[[package]] +name = "zmij" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/userspace/tools/red/Cargo.toml b/userspace/tools/red/Cargo.toml index 1cb86f35..fe1fb7bc 100644 --- a/userspace/tools/red/Cargo.toml +++ b/userspace/tools/red/Cargo.toml @@ -4,16 +4,25 @@ version = "0.1.0" edition = "2024" authors = ["Mark Poliakov "] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] libterm.workspace = true cross.workspace = true +lysp.workspace = true +logsink.workspace = true + thiserror.workspace = true +log.workspace = true unicode-width = "0.1.11" +regex = "1.12.3" -[target.'cfg(not(target_os = "yggdrasil"))'.dependencies] -libc = "0.2.150" -crossterm = "0.27.0" -syslog = "6.1.0" +[features] +default = [] +runtime = [] + +[lints] +workspace = true + +# [target.'cfg(not(target_os = "yggdrasil"))'.dependencies] +# libc = "0.2.150" +# syslog = "6.1.0" diff --git a/userspace/tools/red/runtime/command.lysp b/userspace/tools/red/runtime/command.lysp new file mode 100644 index 00000000..ba4e0cd0 --- /dev/null +++ b/userspace/tools/red/runtime/command.lysp @@ -0,0 +1,30 @@ +(declare-command "q" () (red/quit)) +(declare-command "q!" () (red/quit #t)) +(declare-command + "w" (&optional filename) + (if filename + (red/buffer/write filename) + (red/buffer/write) + ) + ) +(declare-command + "wq" () + (red/buffer/write) + (red/quit) + ) +(declare-command + "e" (&optional filename) + (if filename + (red/buffer/open filename) + ) + ) +(declare-command + "e!" (&optional filename) + (if filename + (red/buffer/open filename #t) + ) + ) +(declare-command + "source" (filename) + (import filename) + ) diff --git a/userspace/tools/red/runtime/core.lysp b/userspace/tools/red/runtime/core.lysp new file mode 100644 index 00000000..053e22fd --- /dev/null +++ b/userspace/tools/red/runtime/core.lysp @@ -0,0 +1,57 @@ +;; External API + +(defmacro ignore (&rest expressions) nil) + +(defmacro declare-key (mode seq action-head &rest action-tail) + (let (bind-function (symbol (+ "red/bind-" (->string mode) "-hook"))) + `(,bind-function ,seq (lambda () (progn ,action-head ,@action-tail))) + ) + ) + +(defmacro declare-command + (command args body-head &rest body-tail) + ;; TODO auto-rest? + `(setq + _red/command-table + (cons + (list ,command (lambda (,@args &rest _) ,body-head ,@body-tail)) + _red/command-table + ) + ) + ) + +(defun try-import (path) + (when (fs/file? path) (import path) #t)) + +(setq _red/command-table nil) + +;; Hooks for the editor/buffer events +(defun _red/lookup-command (name) + (find (lambda (cmd) (= name (car cmd))) _red/command-table) + ) + +(defun _red/root-command-hook (command &rest args) + (let (entry (_red/lookup-command command)) + (if entry + (apply (cadr entry) args) + (red/message (+ "Unhandled command: :" command)) + ) + ) + ) + +(defun _red/root-post-render-hook (width height) + nil + ) + +;; Bind the hooks +(red/bind-command-hook _red/root-command-hook) +(red/bind-post-render-hook _red/root-post-render-hook) + +;; Child modules +(import "keyboard.lysp") +(import "command.lysp") +(import "highlight.lysp") + +;; User configuration +(try-import "/etc/red/init.lysp") +(try-import (+ (fs/home-directory) "/.red.d/init.lysp")) diff --git a/userspace/tools/red/runtime/highlight.lysp b/userspace/tools/red/runtime/highlight.lysp new file mode 100644 index 00000000..8db84eaf --- /dev/null +++ b/userspace/tools/red/runtime/highlight.lysp @@ -0,0 +1,169 @@ +;; TODO +(defun _map (f xs) + (if (nil? xs) + nil + (cons (f (car xs)) (_map f (cdr xs)))) + ) + +(defun _red/syntax-make-keyword-rule (syntax pattern category prev-state next-state) + `(red/syntax/define-keyword-rule ,syntax ,prev-state ,pattern ,category ,next-state)) +(defun _red/syntax-rule-keyword (syntax clause) + ;; (:keyword "PATTERNS"... :category "CATEGORY" :prev-state N :next-state M) + (let ( + prev-state 0 + next-state nil + patterns nil + category nil + ) + (while (not (nil? clause)) + (cond + ; :prev-state N + ((= (car clause) ':prev-state) + (progn + (setq prev-state (cadr clause)) + (setq clause (cdr clause)) + ) + ) + ; :next-state N + ((= (car clause) ':next-state) + (progn + (setq next-state (cadr clause)) + (setq clause (cdr clause)) + ) + ) + ; :category "CATEGORY" + ((= (car clause) ':category) + (progn + (setq category (cadr clause)) + (setq clause (cdr clause)) + ) + ) + ; "PATTERNS"... + (&otherwise (setq patterns (cons (car clause) patterns))) + ) + (setq clause (cdr clause)) + ) + + ; (when (or (nil? category) (nil? patterns)) + ; (error "Invalid clause") + ; ) + + (cons + 'progn + (_map + (lambda (pattern) (_red/syntax-make-keyword-rule syntax pattern category prev-state next-state)) + patterns + ) + ) + ) + ) +(defun _red/syntax-rule-regex (syntax clause) + ;; (:regex "REGEX" "CATEGORY" :prev-state N :next-state M) + (let ( + prev-state 0 + next-state nil + regex (car clause) + category (cadr clause) + ) + (setq clause (cdr (cdr clause))) + (while (not (nil? clause)) + (cond + ; :prev-state N + ((= (car clause) ':prev-state) + (progn + (setq prev-state (cadr clause)) + (setq clause (cdr clause)) + ) + ) + ; :next-state M + ((= (car clause) ':next-state) + (progn + (setq next-state (cadr clause)) + (setq clause (cdr clause)) + ) + ) + ) + (setq clause (cdr clause)) + ) + `(red/syntax/define-regex-rule ,syntax ,prev-state ,regex ,category ,next-state) + ) + ) +(defun _red/syntax-style-category (syntax clause) + ;; ("CATEGORY" :foreground 'COLOR :background 'COLOR :bold) + (let (category (car clause) foreground nil background nil bold nil) + (setq clause (cdr clause)) + (while (not (nil? clause)) + (cond + ; :foreground 'COLOR + ((or (= (car clause) ':foreground) (= (car clause) ':fg)) + (progn + (setq foreground (cadr clause)) + (setq clause (cdr clause)) + ) + ) + ; :background 'COLOR + ((or (= (car clause) ':background) (= (car clause) ':bg)) + (progn + (setq background (cadr clause)) + (setq clause (cdr clause)) + ) + ) + ; :bold + ((= (car clause) ':bold) (setq bold #t)) + ) + (setq clause (cdr clause)) + ) + (when (not (or foreground background bold)) + (error "Empty clause") + ) + `(red/syntax/define-category-style ,syntax ,category ,foreground ,background ,bold) + ) + ) + +(defun _red/define-syntax (syntax clauses) + (let (output nil current-state nil) + (while (cons? clauses) + (let (clause (car clauses)) + (cond + ((= clause ':styles) (setq current-state 'styles)) + ((= clause ':rules) (setq current-state 'rules)) + ;; ("style" ...) clauses + ((= current-state 'styles) (setq output (cons (_red/syntax-style-category syntax clause) output))) + ;; (:regex ...) clauses + ((and + (= current-state 'rules) + (cons? clause) + (= (car clause) ':regex) + (cons? (cdr clause)) + ) + (setq output (cons (_red/syntax-rule-regex syntax (cdr clause)) output)) + ) + ;; (:keyword ...) clauses + ((and + (= current-state 'rules) + (cons? clause) + (= (car clause) ':keyword) + (cons? (cdr clause)) + ) + (setq output (cons (_red/syntax-rule-keyword syntax (cdr clause)) output)) + ) + ) + ) + (setq clauses (cdr clauses)) + ) + (if (nil? output) + nil + (cons 'progn output) + ) + ) + ) +(defmacro define-syntax (syntax &rest clauses) + (_red/define-syntax syntax clauses) + ) + +;; Load syntax files +;; TODO glob +(import "syntax/lysp.lysp") +(import "syntax/rust.lysp") + +(red/syntax/reset) diff --git a/userspace/tools/red/runtime/keyboard.lysp b/userspace/tools/red/runtime/keyboard.lysp new file mode 100644 index 00000000..0d1d78c7 --- /dev/null +++ b/userspace/tools/red/runtime/keyboard.lysp @@ -0,0 +1,60 @@ +(declare-key normal '(z z) + (when (red/buffer/path) + (red/buffer/write) + ) + (red/quit) + ) +(declare-key normal '(Z Z) (red/quit #t)) + +(declare-key normal 'a (red/buffer/set-mode 'insert-after)) +(declare-key normal 'i (red/buffer/set-mode 'insert-before)) +(declare-key normal 'I + (red/buffer/move 'line-start) + (red/buffer/set-mode 'insert-before)) +(declare-key normal 'A + (red/buffer/move 'line-end) + (red/buffer/set-mode 'insert-after)) + +(declare-key normal '(g g) (red/buffer/move 'first-line)) +(declare-key normal 'G (red/buffer/move 'last-line)) + +(declare-key normal 'h (red/buffer/move 'prev-char)) +(declare-key normal 'l (red/buffer/move 'next-char)) +(declare-key normal 'k (red/buffer/move 'prev-line)) +(declare-key normal 'j (red/buffer/move 'next-line)) +(declare-key normal 'J (red/buffer/move 'next-page)) +(declare-key normal 'K (red/buffer/move 'prev-page)) +(declare-key normal 'o + (red/buffer/insert-line-after) + (red/buffer/move 'next-line) + (red/buffer/set-mode 'insert-before)) +(declare-key normal 'O + (red/buffer/insert-line-before) + (red/buffer/set-mode 'insert-before)) +(declare-key normal '(d d) + (red/buffer/kill-line) + (red/buffer/move 'prev-line)) +(declare-key normal 'newline + (red/buffer/move 'next-line) + (red/buffer/move 'line-start)) + +(declare-key normal 'left (red/buffer/move 'prev-char)) +(declare-key normal 'right (red/buffer/move 'next-char)) +(declare-key normal 'up (red/buffer/move 'prev-line)) +(declare-key normal 'down (red/buffer/move 'next-line)) +(declare-key normal 'home (red/buffer/move 'line-start)) +(declare-key normal 'end (red/buffer/move 'line-end)) + +(declare-key insert 'left (red/buffer/move 'prev-char)) +(declare-key insert 'right (red/buffer/move 'next-char)) +(declare-key insert 'up (red/buffer/move 'prev-line)) +(declare-key insert 'down (red/buffer/move 'next-line)) +(declare-key insert 'home (red/buffer/move 'line-start)) +(declare-key insert 'end (red/buffer/move 'line-end)) +(declare-key insert 'backspace (red/buffer/erase-backward)) +(declare-key insert 'newline + (red/buffer/insert-line-after #t) + (red/buffer/move 'next-line) + (red/buffer/move 'line-start) + (red/buffer/set-mode 'insert-before) + ) diff --git a/userspace/tools/red/runtime/syntax/lysp.lysp b/userspace/tools/red/runtime/syntax/lysp.lysp new file mode 100644 index 00000000..32e32e07 --- /dev/null +++ b/userspace/tools/red/runtime/syntax/lysp.lysp @@ -0,0 +1,45 @@ +;; Test expression for syntax highlight +(ignore (let print 1234 0x1234 "a string" empty-string "" after-empty-string #t #F)) + +(define-syntax + "lysp" + :styles + ("keyword" :fg 'red) + ("symbol" :fg 'cyan) + ("comment" :fg 'white :bold) + ("string" :fg 'yellow) + ("custom-syntax" :fg 'red :bold) + ("number" :fg 'cyan :bold) + ("constant" :fg 'cyan :bold) + :rules + ;; keywords + (:keyword + "let" "let*" "progn" "defun" "defmacro" "setq" + "when" "unless" "if" "while" "loop" "&optional" + "&rest" "&otherwise" "cond" "nil" "lambda" "ignore" + :category "keyword" + ) + ;; prelude functions + (:keyword + "car" "cdr" "caar" "cadr" "cdar" "cddr" "cadar" + "caddr" "list?" "nil?" "cons?" "string?" "symbol?" + "cons" "list" "print" "not" "or" "and" "=" ">=" + "<=" "/=" "error" "import" "apply" "find" + :category "symbol" + ) + (:keyword + "#t" "#T" "#f" "#F" + :category "constant" + ) + ;; strings + (:regex "." "string" :prev-state 1) + (:regex "[^\\\\]\"" "string" :prev-state 1 :next-state 0) + (:regex "\"" "string" :next-state 1) + (:regex "\"\"" "string") + ;; :keywords + (:regex ":[\\-\\w]+" "custom-syntax") + ;; numbers + (:regex "(0x|0o|0b)?\\d+" "number") + ;; comments + (:regex ";.*$" "comment") + ) diff --git a/userspace/tools/red/runtime/syntax/rust.lysp b/userspace/tools/red/runtime/syntax/rust.lysp new file mode 100644 index 00000000..af9a09dd --- /dev/null +++ b/userspace/tools/red/runtime/syntax/rust.lysp @@ -0,0 +1,37 @@ +;; TODO very WIP +(define-syntax + "rust" + :styles + ("keyword" :fg 'red) + ("symbol" :fg 'cyan) + ("comment" :fg 'white :bold) + ("string" :fg 'yellow) + ("number" :fg 'cyan :bold) + ("constant" :fg 'cyan :bold) + :rules + ;; Keywords + (:keyword + "use" "pub" "mod" "enum" "struct" "impl" + "for" "=>" "->" "match" "for" "while" "loop" + "if" "fn" "&mut" "&" "let" "where" "?" + :category "keyword" + ) + ;; Identifiers + (:keyword + "Rc" "RefCell" "Clone" "Copy" "PartialEq" "Eq" + "PartialOrd" "Ord" "Debug" "Result" "Option" + "bool" "i8" "i16" "i32" "i64" "i128" "u8" "u16" + "u32" "u64" "u128" "str" "&str" + "Self" "self" "&self" "&mut self" "Ok" "Err" + :category "symbol" + ) + (:keyword "true" "false" :category "constant") + ;; strings + (:regex "." "string" :prev-state 1) + (:regex "[^\\\\]*\"" "string" :prev-state 1 :next-state 0) + (:regex "\"" "string" :next-state 1) + ;; numbers + (:regex "(0x|0o|0b)?\\d+" "number") + ;; Comments + (:regex "//.*$" "comment") + ) diff --git a/userspace/tools/red/src/buffer/line.rs b/userspace/tools/red/src/buffer/line.rs new file mode 100644 index 00000000..f9929a38 --- /dev/null +++ b/userspace/tools/red/src/buffer/line.rs @@ -0,0 +1,104 @@ +use std::{ + fmt, + ops::Index, + slice::{self, SliceIndex}, +}; + +use crate::{ + highlight::LineHighlight, + text::{Span, Text, TextLike, TextLikeMut}, +}; + +pub struct Line { + pub text: Text, + pub highlight: LineHighlight, + pub highlight_dirty: bool, +} + +impl Line { + pub fn new() -> Self { + Self::from(Text::new()) + } + + #[inline] + pub fn len(&self) -> usize { + self.text.len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.text.is_empty() + } +} + +impl TextLike for Line { + type Iter<'a> + = slice::Iter<'a, char> + where + Self: 'a; + type Span<'a> + = Span<'a> + where + Self: 'a; + + #[inline] + fn display_width(&self, tab_width: usize) -> usize { + self.text.display_width(tab_width) + } + #[inline] + fn iter(&self) -> Self::Iter<'_> { + self.text.iter() + } + #[inline] + fn span>(&self, range: R) -> Self::Span<'_> { + self.text.span(range) + } + #[inline] + fn skip_to_width(&self, offset: usize, tab_width: usize) -> (Self::Span<'_>, usize, usize) { + self.text.skip_to_width(offset, tab_width) + } +} + +impl TextLikeMut for Line { + fn split_off(&mut self, at: usize) -> Self { + let tail = self.text.split_off(at); + self.highlight_dirty = true; + Self::from(tail) + } + fn extend(&mut self, other: Self) { + self.text.extend(other.text); + self.highlight_dirty = true; + } + fn remove(&mut self, at: usize) { + self.text.remove(at); + self.highlight_dirty = true; + } + fn insert(&mut self, at: usize, ch: char) { + self.text.insert(at, ch); + self.highlight_dirty = true; + } +} + +impl From for Line { + fn from(text: Text) -> Self { + Self { + text, + highlight: LineHighlight::default(), + highlight_dirty: true, + } + } +} + +impl Index for Line { + type Output = char; + + fn index(&self, index: usize) -> &Self::Output { + &self.text[index] + } +} + +impl fmt::Display for Line { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.text, f) + } +} diff --git a/userspace/tools/red/src/buffer/mod.rs b/userspace/tools/red/src/buffer/mod.rs index 06f96cc5..584de4e6 100644 --- a/userspace/tools/red/src/buffer/mod.rs +++ b/userspace/tools/red/src/buffer/mod.rs @@ -4,17 +4,23 @@ use std::{ fs::File, io::{self, BufRead, BufReader, BufWriter, Write as IoWrite}, path::{Path, PathBuf}, + rc::Rc, }; use libterm::{Color, CursorStyle, Term}; use unicode_width::UnicodeWidthChar; use crate::{ - config::Config, + buffer::{line::Line, style::Style}, + config::EditorConfig, error::Error, - line::{Line, TextLike}, + highlight::Highlighter, + text::{Text, TextLike, TextLikeMut}, }; +pub mod line; +pub mod style; + #[derive(Default)] pub struct View { cursor_column: usize, @@ -47,6 +53,7 @@ pub struct Buffer { name: Option, path: Option, modified: bool, + filetype: Rc, } impl Mode { @@ -69,7 +76,7 @@ impl View { } } - pub fn set_column(&mut self, config: &Config, col: usize, line: Option<&Line>) { + pub fn set_column(&mut self, config: &EditorConfig, col: usize, line: Option<&Line>) { let Some(line) = line else { self.column_offset = 0; self.cursor_column = 0; @@ -121,6 +128,7 @@ impl Buffer { name: None, path: None, modified: false, + filetype: "text".into(), } } @@ -129,7 +137,8 @@ impl Buffer { let lines = input.lines().collect::, _>>()?; let lines = lines .into_iter() - .map(|line| Line::from_str(line.trim_end_matches('\n'))) + .map(|line| Text::from_str(line.trim_end_matches('\n'))) + .map(Line::from) .collect(); Ok(lines) } @@ -143,7 +152,7 @@ impl Buffer { vec![] }; - Ok(Self { + let mut this = Self { lines, name, path: Some(path.into()), @@ -152,7 +161,13 @@ impl Buffer { mode_dirty: true, view: View::default(), modified: false, - }) + filetype: "text".into(), + }; + + this.update_filetype(); + this.reset_highlight(); + + Ok(this) } pub fn reopen>(&mut self, path: P) -> io::Result<()> { @@ -173,10 +188,34 @@ impl Buffer { self.path = Some(path.into()); self.name = name; + self.update_filetype(); + self.reset_highlight(); Ok(()) } + pub fn update_filetype(&mut self) { + let extension = self + .path + .as_ref() + .and_then(|path| path.extension()) + .and_then(|ext| ext.to_str()); + + let filetype = match extension { + Some("lysp") => "lysp", + Some("rs") => "rust", + _ => "text", + }; + + self.filetype = filetype.into(); + } + + pub fn reset_highlight(&mut self) { + for line in self.lines.iter_mut() { + line.highlight_dirty = true; + } + } + pub fn save(&mut self) -> Result<(), Error> { let path = self.path.as_ref().ok_or(Error::NoPath)?; let mut writer = BufWriter::new(File::create(path).map_err(Error::WriteError)?); @@ -201,7 +240,7 @@ impl Buffer { self.name = name; } - pub fn set_mode(&mut self, config: &Config, mode: SetMode) { + pub fn set_mode(&mut self, config: &EditorConfig, mode: SetMode) { let dst_mode = match mode { SetMode::Normal => Mode::Normal, SetMode::InsertAfter | SetMode::InsertBefore => Mode::Insert, @@ -220,6 +259,10 @@ impl Buffer { } } + pub fn filetype(&self) -> &str { + &self.filetype + } + pub fn mode(&self) -> Mode { self.mode } @@ -248,7 +291,7 @@ impl Buffer { self.view.cursor_row } - pub fn set_position(&mut self, config: &Config, px: usize, py: usize) { + pub fn set_position(&mut self, config: &EditorConfig, px: usize, py: usize) { self.dirty = true; if self.lines.is_empty() { @@ -275,7 +318,7 @@ impl Buffer { } } - pub fn to_line_end(&mut self, config: &Config) { + pub fn to_line_end(&mut self, config: &EditorConfig) { let len = self .lines .get(self.view.cursor_row) @@ -285,7 +328,7 @@ impl Buffer { self.set_position(config, len, self.view.cursor_row); } - pub fn to_first_line(&mut self, config: &Config) { + pub fn to_first_line(&mut self, config: &EditorConfig) { let len = self .lines .get(self.view.cursor_row) @@ -295,7 +338,7 @@ impl Buffer { self.set_position(config, len, 0); } - pub fn to_last_line(&mut self, config: &Config) { + pub fn to_last_line(&mut self, config: &EditorConfig) { if self.lines.is_empty() { return; } @@ -309,18 +352,18 @@ impl Buffer { self.set_position(config, len, self.lines.len() - 1); } - pub fn set_column(&mut self, config: &Config, x: usize) { + pub fn set_column(&mut self, config: &EditorConfig, x: usize) { self.set_position(config, x, self.view.cursor_row); } - pub fn move_cursor(&mut self, config: &Config, dx: isize, dy: isize) { + pub fn move_cursor(&mut self, config: &EditorConfig, dx: isize, dy: isize) { let px = (self.view.cursor_column as isize + dx).max(0) as usize; let py = (self.view.cursor_row as isize + dy).max(0) as usize; self.set_position(config, px, py); } - pub fn resize(&mut self, config: &Config, offset_x: usize, width: usize, height: usize) { + pub fn resize(&mut self, config: &EditorConfig, offset_x: usize, width: usize, height: usize) { self.dirty = true; self.view.height = height; self.view.width = width; @@ -334,7 +377,7 @@ impl Buffer { ); } - pub fn display_cursor(&self, config: &Config) -> (usize, usize) { + pub fn display_cursor(&self, config: &EditorConfig) -> (usize, usize) { if self.lines.is_empty() { return (0, 0); } @@ -374,23 +417,56 @@ impl Buffer { fn display_line( &self, - config: &Config, + config: &EditorConfig, + current_style: &mut Style, term: &mut Term, row: usize, line: &Line, + hi: &Highlighter, ) -> Result<(), Error> { let mut pos = 0; term.set_cursor_position(row, self.view.offset_x) .map_err(Error::TerminalError)?; - let span = line.skip_to_width(self.view.column_offset, config.tab_width); + let (span, _skipped_display_cells, skipped_characters) = + line.skip_to_width(self.view.column_offset, config.tab_width); let long_line = span.display_width(config.tab_width) > self.view.width; - for &ch in span.iter() { + let mut current_category: Option<&str> = None; + + // Reset style + let reset_style = Style::default(); + reset_style + .apply_delta(current_style, term) + .map_err(Error::TerminalError)?; + *current_style = reset_style; + + for (index, &ch) in span.iter().enumerate() { if pos >= self.view.width { break; } + let character_index = skipped_characters + index; + + let matching_token = line + .highlight + .tokens + .iter() + .find(|t| t.range.contains(&character_index)); + let token_category = matching_token.map(|t| t.category.as_ref()); + + if token_category != current_category { + current_category = token_category; + let style = token_category + .and_then(|cat| hi.stylize(&self.filetype, cat, current_style)) + .unwrap_or_else(Style::default); + + style + .apply_delta(current_style, term) + .map_err(Error::TerminalError)?; + *current_style = style; + } + if ch == '\t' { let old_pos = pos; let new_pos = (pos + config.tab_width) & !(config.tab_width - 1); @@ -406,6 +482,7 @@ impl Buffer { term.write_char('>').map_err(Error::TerminalFmtError)?; term.set_foreground(Color::Default) .map_err(Error::TerminalError)?; + current_style.foreground = Color::Default; } else { term.write_char(' ').map_err(Error::TerminalFmtError)?; } @@ -433,7 +510,14 @@ impl Buffer { Ok(()) } - pub fn display(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> { + pub fn display( + &mut self, + config: &EditorConfig, + term: &mut Term, + hi: &Highlighter, + ) -> Result<(), Error> { + hi.rehighlight(&mut self.lines, &self.filetype); + match self.mode { Mode::Normal => { term.set_cursor_style(CursorStyle::Default) @@ -445,6 +529,8 @@ impl Buffer { } } + let mut current_style = Style::default(); + for (row, line) in self .lines .iter() @@ -452,13 +538,27 @@ impl Buffer { .take(self.view.height) .enumerate() { - self.display_line(config, term, row, line)?; + self.display_line(config, &mut current_style, term, row, line, hi)?; } + // Reset to default style + Style::DEFAULT + .apply_delta(¤t_style, term) + .map_err(Error::TerminalError)?; + Ok(()) } - pub fn set_terminal_cursor(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> { + pub fn get_terminal_cursor(&self, config: &EditorConfig) -> (usize, usize) { + let (x, y) = self.display_cursor(config); + (x + self.view.offset_x, y) + } + + pub fn set_terminal_cursor( + &mut self, + config: &EditorConfig, + term: &mut Term, + ) -> Result<(), Error> { let (x, y) = self.display_cursor(config); if self.mode_dirty { match self.mode { @@ -497,7 +597,7 @@ impl Buffer { self.lines.insert(self.view.cursor_row + 1, newline); } - pub fn insert(&mut self, config: &Config, ch: char) { + pub fn insert(&mut self, config: &EditorConfig, ch: char) { if self.lines.is_empty() { assert_eq!(self.view.cursor_row, 0); self.lines.push(Line::new()); @@ -509,7 +609,7 @@ impl Buffer { self.modified = true; } - pub fn erase_backward(&mut self, config: &Config) { + pub fn erase_backward(&mut self, config: &EditorConfig) { if self.lines.is_empty() { return; } @@ -547,12 +647,15 @@ impl Buffer { self.modified = true; } - pub fn kill_line(&mut self, config: &Config) { + pub fn kill_line(&mut self, config: &EditorConfig) { if self.lines.is_empty() { return; } self.lines.remove(self.view.cursor_row); + if self.view.cursor_row < self.lines.len() { + self.lines[self.view.cursor_row].highlight_dirty = true; + } self.move_cursor(config, 0, 1); self.modified = true; } diff --git a/userspace/tools/red/src/buffer/style.rs b/userspace/tools/red/src/buffer/style.rs new file mode 100644 index 00000000..a8975927 --- /dev/null +++ b/userspace/tools/red/src/buffer/style.rs @@ -0,0 +1,45 @@ +use std::io; + +use libterm::{Color, Term}; + +#[derive(Clone, Copy)] +pub struct Style { + pub foreground: Color, + pub background: Color, + pub bold: bool, +} + +impl Default for Style { + fn default() -> Self { + Self::DEFAULT + } +} + +impl Style { + pub const DEFAULT: Self = Self { + foreground: Color::White, + background: Color::Black, + bold: false, + }; + + pub fn apply(&self, term: &mut Term) -> Result<(), io::Error> { + self.apply_delta(&Self::DEFAULT, term) + } + + pub fn apply_delta(&self, original: &Self, term: &mut Term) -> Result<(), io::Error> { + // TODO libterm Color PartialEq + if self.foreground as u32 != original.foreground as u32 { + // Apply foreground + term.set_foreground(self.foreground)?; + } + if self.background as u32 != original.background as u32 { + // Apply background + term.set_background(self.background)?; + } + if self.bold != original.bold { + // Apply bold + term.set_bright(self.bold)?; + } + Ok(()) + } +} diff --git a/userspace/tools/red/src/command.rs b/userspace/tools/red/src/command.rs index a0f1014e..67f002dd 100644 --- a/userspace/tools/red/src/command.rs +++ b/userspace/tools/red/src/command.rs @@ -1,144 +1,20 @@ -use std::ops::RangeInclusive; +use lysp::vm::value::convert::AnyFunction; -use crate::{ - State, - buffer::{Buffer, SetMode}, - config::Config, - error::Error, -}; +use crate::{State, error::Error}; pub type CommandFn = fn(&mut State, &[&str]) -> Result<(), Error>; -#[derive(Debug, Clone, Copy, PartialEq)] pub enum Action { - // Editing - EraseBackward, - InsertBefore, - InsertAfter, - NewlineBefore, - NewlineAfter, - BreakLine, - KillLine, - - // Movement - MoveFirstLine, - MoveLastLine, - MoveCharPrev, - MoveCharNext, - MoveLineBack(usize), - MoveLineForward(usize), - MoveLineStart, - MoveLineEnd, + Handler(AnyFunction), + Command(String), + None, } -static COMMANDS: &[(&str, RangeInclusive, CommandFn)] = &[ - ("w", 0..=1, cmd_write), - ("w!", 0..=1, cmd_force_write), - ("q", 0..=0, cmd_exit), - ("q!", 0..=0, cmd_force_exit), - ("e", 1..=1, cmd_edit), - ("e!", 0..=1, cmd_force_edit), -]; - -// Commands -fn cmd_write(state: &mut State, args: &[&str]) -> Result<(), Error> { - if args.len() == 1 && state.buffer().is_modified() && state.buffer().path().is_some() { - return Err(Error::UnsavedBuffer( - "Use :w! FILE to force write to another file", - )); - } - - cmd_force_write(state, args) -} - -fn cmd_force_write(state: &mut State, args: &[&str]) -> Result<(), Error> { - let buffer = state.buffer_mut(); - if let Some(&path) = args.first() { - buffer.set_path(path); - } - - buffer.save()?; - - if let Some(name) = buffer.name() { - let status = format!("{:?} written", name); - state.set_status(status); - } - - Ok(()) -} - -fn cmd_edit(state: &mut State, args: &[&str]) -> Result<(), Error> { - if state.buffer().is_modified() { - return Err(Error::UnsavedBuffer("Use :e! [FILE] to open another file")); - } - - cmd_force_edit(state, args) -} - -fn cmd_force_edit(state: &mut State, args: &[&str]) -> Result<(), Error> { - if let Some(&path) = args.first() { - state.buffer_mut().reopen(path).map_err(Error::OpenError) - } else if let Some(path) = state.buffer().path().cloned() { - state.buffer_mut().reopen(path).map_err(Error::OpenError) - } else { - Err(Error::NoPath) - } -} - -fn cmd_exit(state: &mut State, _args: &[&str]) -> Result<(), Error> { - let buffer = state.buffer(); - - if buffer.is_modified() { - return Err(Error::UnsavedBuffer("Use :q! to force exit")); - } - - state.exit(); - Ok(()) -} - -fn cmd_force_exit(state: &mut State, _args: &[&str]) -> Result<(), Error> { - state.exit(); - Ok(()) -} - -pub fn execute(state: &mut State, command: String) -> Result<(), Error> { - let words = command.split(' ').collect::>(); - let Some((&cmd, args)) = words.split_first() else { - return Ok(()); - }; - - for (name, nargs, f) in COMMANDS.iter() { - if *name == cmd { - if !nargs.contains(&args.len()) { - todo!(); - } - - return f(state, args); +impl From> for Action { + fn from(value: Option) -> Self { + match value { + Some(handler) => Self::Handler(handler), + None => Self::None, } } - - Err(Error::UnknownCommand(cmd.into())) -} - -pub fn perform(buffer: &mut Buffer, config: &Config, action: Action) -> Result<(), Error> { - match action { - // Editing - Action::EraseBackward => buffer.erase_backward(config), - Action::InsertBefore => buffer.set_mode(config, SetMode::InsertBefore), - Action::InsertAfter => buffer.set_mode(config, SetMode::InsertAfter), - Action::NewlineBefore => buffer.newline_before(), - Action::NewlineAfter => buffer.newline_after(false), - Action::BreakLine => buffer.newline_after(true), - Action::KillLine => buffer.kill_line(config), - // Movement - Action::MoveCharPrev => buffer.move_cursor(config, -1, 0), - Action::MoveCharNext => buffer.move_cursor(config, 1, 0), - Action::MoveLineBack(count) => buffer.move_cursor(config, 0, -(count as isize)), - Action::MoveLineForward(count) => buffer.move_cursor(config, 0, count as isize), - Action::MoveLineStart => buffer.set_column(config, 0), - Action::MoveLineEnd => buffer.to_line_end(config), - Action::MoveFirstLine => buffer.to_first_line(config), - Action::MoveLastLine => buffer.to_last_line(config), - } - Ok(()) } diff --git a/userspace/tools/red/src/config.rs b/userspace/tools/red/src/config.rs index fc91e8bb..adc3e6b2 100644 --- a/userspace/tools/red/src/config.rs +++ b/userspace/tools/red/src/config.rs @@ -1,80 +1,127 @@ -use libterm::TermKey; - -use crate::{ - buffer::Mode, - command::Action, - keymap::{KeyMap, KeySeq, PrefixNode, bind1, bindn}, +use std::{ + mem, + ops::{Deref, DerefMut}, }; -pub struct Config { - // TODO must be a power of 2, lol +pub struct EditorConfig { pub tab_width: usize, - pub number: bool, - - pub nmap: KeyMap, - pub imap: KeyMap, + pub number: Dirty, } -impl Default for Config { +pub struct Dirty(T, bool); + +impl Default for EditorConfig { fn default() -> Self { - use Action::*; - - let nmap = KeyMap::from_iter([ - bind1('i', [InsertBefore]), - bind1('a', [InsertAfter]), - bind1('h', [MoveCharPrev]), - bind1('l', [MoveCharNext]), - bind1('j', [MoveLineForward(1)]), - bind1('J', [MoveLineForward(25)]), - bind1('k', [MoveLineBack(1)]), - bind1('K', [MoveLineBack(25)]), - bind1(TermKey::Left, [MoveCharPrev]), - bind1(TermKey::Right, [MoveCharNext]), - bind1(TermKey::Up, [MoveLineBack(1)]), - bind1(TermKey::Down, [MoveLineForward(1)]), - bind1(TermKey::Home, [MoveLineStart]), - bind1(TermKey::End, [MoveLineEnd]), - bindn(['g', 'g'], [MoveFirstLine]), - bind1('G', [MoveLastLine]), - bind1('I', [MoveLineStart, InsertBefore]), - bind1('A', [MoveLineEnd, InsertAfter]), - bind1('o', [NewlineAfter, MoveLineForward(1), InsertBefore]), - bind1('O', [NewlineBefore, MoveLineBack(1), InsertBefore]), - bindn(['d', 'd'], [KillLine]), - ]); - - let imap = KeyMap::from_iter([ - bind1('\x7F', [EraseBackward]), - bind1(TermKey::Left, [MoveCharPrev]), - bind1(TermKey::Right, [MoveCharNext]), - bind1(TermKey::Up, [MoveLineBack(1)]), - bind1(TermKey::Down, [MoveLineForward(1)]), - bind1(TermKey::Home, [MoveLineStart]), - bind1(TermKey::End, [MoveLineEnd]), - bind1( - '\n', - [BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore], - ), - bind1( - '\x0D', - [BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore], - ), - ]); - Self { tab_width: 4, - number: true, - nmap, - imap, + number: Dirty::new(false), } } } -impl Config { - pub fn key_seq(&self, mode: Mode, seq: &KeySeq) -> Option<&PrefixNode>> { - match mode { - Mode::Normal => self.nmap.get(seq), - Mode::Insert => self.imap.get(seq), - } +impl Dirty { + pub const fn new(value: T) -> Self { + Self(value, true) + } + + pub fn clean(&mut self) -> bool { + mem::replace(&mut self.1, false) } } + +impl Deref for Dirty { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Dirty { + fn deref_mut(&mut self) -> &mut Self::Target { + self.1 = true; + &mut self.0 + } +} + +// +// use libterm::TermKey; +// +// use crate::{ +// buffer::Mode, +// command::Action, +// keymap::{KeyMap, KeySeq, PrefixNode, bind1, bindn}, +// }; +// +// pub struct Config { +// // TODO must be a power of 2, lol +// pub tab_width: usize, +// pub number: bool, +// +// pub nmap: KeyMap, +// pub imap: KeyMap, +// } +// +// impl Default for Config { +// fn default() -> Self { +// use Action::*; +// +// let nmap = KeyMap::from_iter([ +// bind1('i', [InsertBefore]), +// bind1('a', [InsertAfter]), +// bind1('h', [MoveCharPrev]), +// bind1('l', [MoveCharNext]), +// bind1('j', [MoveLineForward(1)]), +// bind1('J', [MoveLineForward(25)]), +// bind1('k', [MoveLineBack(1)]), +// bind1('K', [MoveLineBack(25)]), +// bind1(TermKey::Left, [MoveCharPrev]), +// bind1(TermKey::Right, [MoveCharNext]), +// bind1(TermKey::Up, [MoveLineBack(1)]), +// bind1(TermKey::Down, [MoveLineForward(1)]), +// bind1(TermKey::Home, [MoveLineStart]), +// bind1(TermKey::End, [MoveLineEnd]), +// bindn(['g', 'g'], [MoveFirstLine]), +// bind1('G', [MoveLastLine]), +// bind1('I', [MoveLineStart, InsertBefore]), +// bind1('A', [MoveLineEnd, InsertAfter]), +// bind1('o', [NewlineAfter, MoveLineForward(1), InsertBefore]), +// bind1('O', [NewlineBefore, MoveLineBack(1), InsertBefore]), +// bindn(['d', 'd'], [KillLine]), +// ]); +// +// let imap = KeyMap::from_iter([ +// bind1('\x7F', [EraseBackward]), +// bind1(TermKey::Left, [MoveCharPrev]), +// bind1(TermKey::Right, [MoveCharNext]), +// bind1(TermKey::Up, [MoveLineBack(1)]), +// bind1(TermKey::Down, [MoveLineForward(1)]), +// bind1(TermKey::Home, [MoveLineStart]), +// bind1(TermKey::End, [MoveLineEnd]), +// bind1( +// '\n', +// [BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore], +// ), +// bind1( +// '\x0D', +// [BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore], +// ), +// ]); +// +// Self { +// tab_width: 4, +// number: true, +// nmap, +// imap, +// } +// } +// } +// +// impl Config { +// pub fn key_seq(&self, mode: Mode, seq: &KeySeq) -> Option<&PrefixNode>> { +// match mode { +// Mode::Normal => self.nmap.get(seq), +// Mode::Insert => self.imap.get(seq), +// } +// } +// } diff --git a/userspace/tools/red/src/error.rs b/userspace/tools/red/src/error.rs index 06bfd2ad..c1f9ec2e 100644 --- a/userspace/tools/red/src/error.rs +++ b/userspace/tools/red/src/error.rs @@ -1,5 +1,7 @@ use std::{fmt, io}; +use lysp::error::{MachineError, ValueConversionError}; + #[derive(Debug, thiserror::Error)] pub enum Error { // I/O errors @@ -19,4 +21,13 @@ pub enum Error { TerminalError(io::Error), #[error("Terminal error: {0:?}")] TerminalFmtError(fmt::Error), + + #[error("Scripting error: {0:?}")] + Script(#[from] MachineError), +} + +impl From for Error { + fn from(value: ValueConversionError) -> Self { + Self::Script(value.into()) + } } diff --git a/userspace/tools/red/src/highlight.rs b/userspace/tools/red/src/highlight.rs new file mode 100644 index 00000000..eb09fb1d --- /dev/null +++ b/userspace/tools/red/src/highlight.rs @@ -0,0 +1,269 @@ +use std::{collections::HashMap, ops::Range, rc::Rc}; + +use libterm::Color; +use regex::Regex; + +use crate::buffer::{line::Line, style::Style}; + +#[derive(Default)] +pub struct Highlighter { + syntaxes: HashMap, Syntax>, +} + +pub struct TokenSpan { + pub range: Range, + pub category: Rc, +} + +#[derive(Default)] +pub struct LineHighlight { + pub start_state: u32, + pub end_state: u32, + pub tokens: Vec, + pub dirty: bool, +} + +enum SyntaxPattern { + Keyword(Rc), + Regex(Regex), +} + +pub struct SyntaxRule { + pattern: SyntaxPattern, + next_state: Option, + category: Rc, +} + +#[derive(Default)] +pub struct SyntaxStyle { + pub foreground: Option, + pub background: Option, + pub bold: Option, +} + +#[derive(Default)] +pub struct Syntax { + state_rules: HashMap>, + style_rules: HashMap, SyntaxStyle>, +} + +impl SyntaxStyle { + pub fn apply(&self, base: &Style) -> Style { + let mut result = *base; + if let Some(foreground) = self.foreground { + result.foreground = foreground; + } + if let Some(background) = self.background { + result.background = background; + } + if let Some(bold) = self.bold { + result.bold = bold; + } + result + } +} + +impl SyntaxRule { + pub fn keyword>, C: Into>>( + keyword: S, + next_state: Option, + category: C, + ) -> Self { + Self { + pattern: SyntaxPattern::Keyword(keyword.into()), + next_state, + category: category.into(), + } + } + + pub fn regex>>( + regex: &str, + next_state: Option, + category: C, + ) -> Option { + Some(Self { + pattern: SyntaxPattern::Regex(Regex::new(regex).ok()?), + next_state, + category: category.into(), + }) + } +} + +impl Highlighter { + pub fn define_rule(&mut self, filetype: Rc, entry_state: u32, rule: SyntaxRule) { + self.syntaxes + .entry(filetype) + .or_default() + .define_rule(entry_state, rule); + } + + pub fn define_category(&mut self, filetype: Rc, category: Rc, style: SyntaxStyle) { + self.syntaxes + .entry(filetype) + .or_default() + .define_style(category, style); + } + + pub fn stylize(&self, filetype: &str, category: &str, base: &Style) -> Option