use std::{ collections::{HashMap, VecDeque}, io::{BufRead, BufReader}, num::NonZeroU8, ops::Range, rc::Rc, sync::{LazyLock, RwLock, mpsc::Receiver}, vec::Drain, }; use deteregex::Regex; use happylock::ThreadKey; use uuid::Uuid; use crate::pipe::{Message, MessageField, VirtualFile}; static OPEN_BUFFERS: LazyLock>> = LazyLock::new(|| RwLock::new(HashMap::new())); #[derive(Default)] struct ProgramState { open_files: HashMap, most_recent_file: Option, } struct FileBuffer { current_line_number: usize, file: Option>, lines: Vec, } #[derive(Debug, Default, Clone)] struct LineBuffer { labels: Vec, content: String, } impl ProgramState { fn next_buffer_id(&mut self) -> usize { let mut id = self.open_files.len(); while self.open_files.contains_key(&id) { id = id.wrapping_add(self.open_files.len()); } self.most_recent_file = Some(id); id } fn buffer_mut(&mut self, buffer: Option) -> Result<&mut FileBuffer, Rc> { let buffer_idx = buffer.or(self.most_recent_file).ok_or("no open file")?; let buffer = self .open_files .get_mut(&buffer_idx) .ok_or("invalid buffer id")?; self.most_recent_file = Some(buffer_idx); Ok(buffer) } fn close_buffer(&mut self, buffer: Option) -> Result<(), Rc> { let buffer = buffer.or(self.most_recent_file).ok_or("no open file")?; self.open_files.remove(&buffer).ok_or("invalid buffer id")?; Ok(()) } } impl FileBuffer { fn new() -> Self { Self { lines: vec![LineBuffer::new()], current_line_number: 0, file: None, } } fn open(mut file: Box) -> std::io::Result { let mut buf = String::new(); file.read_to_string(&mut buf)?; let lines = buf.split('\n'); Ok(Self { lines: lines .map(|line| -> Result { Ok(LineBuffer { labels: Vec::new(), content: line.to_string(), }) }) .collect::>()?, file: Some(file), current_line_number: 0, }) } fn write(&mut self) -> Result<(), Rc> { let Some(file) = self.file.as_deref_mut() else { return Err("A file must be opened first".into()); }; file.write_all( self.lines .iter() .fold( String::with_capacity( self.lines.iter().map(|line| line.content.len() + 1).sum(), ), |mut output, line| { output.push('\n'); output.push_str(&line.content); output }, ) .as_bytes(), ) .map_err(|e| e.to_string().into()) } fn get_line_index(&self, index: &LineIndex) -> Result> { match index { LineIndex::Current => Ok(self.current_line_number), LineIndex::Last => (!self.lines.is_empty()) .then_some(self.lines.len() - 1) .ok_or("file is empty".into()), LineIndex::Number(i) => usize::try_from(*i).map_err(|e| e.to_string().into()), LineIndex::CurrentPlus(amount) => { TryInto::::try_into(self.current_line_number as i32 + amount) .map_err(|e| e.to_string().into()) } LineIndex::Bookmark(label) => self .lines .iter() .enumerate() .find(|(_i, line)| line.labels.contains(label)) .map(|(i, _line)| i) .ok_or(format!("bookmark with label \"{label}\" does not exist").into()), LineIndex::FirstRegex(regex) => self .lines .iter() .enumerate() .skip(self.current_line_number + 1) .chain( self.lines .iter() .enumerate() .take(self.current_line_number + 1), ) .find(|(_i, line)| regex.is_match(&line.content)) .map(|(i, _line)| i) .ok_or("no match found".to_string().into()), LineIndex::LastRegex(regex) => self .lines .iter() .enumerate() .take(self.current_line_number) .chain(self.lines.iter().enumerate().skip(self.current_line_number)) .rev() .find(|(_i, line)| regex.is_match(&line.content)) .map(|(i, _line)| i) .ok_or("no match found".into()), } } fn get_line_range(&self, range: &LineRange) -> Result, Rc> { Ok(self.get_line_index(&range.start)?..self.get_line_index(&range.end)?) } fn set_file(&mut self, file: Box) { self.file = Some(file); } fn current_line_number(&self) -> usize { self.current_line_number } fn set_current_line(&mut self, index: usize) { self.current_line_number = index; } fn add_at(&mut self, index: usize, content: impl AsRef) -> usize { let new_lines = LineBuffer::from_str(content.as_ref()).collect::>(); let new_lines_len = new_lines.len(); self.lines.splice(index..index, new_lines); new_lines_len } fn find(&self, range: Range, regex: &Regex) -> impl Iterator { self.lines[range] .iter() .enumerate() .filter_map(|(i, line)| regex.is_match(&line.content).then_some(i)) } fn replace_all(&mut self, range: Range, pattern: &Regex, replacement: &str) { for line in &mut self.lines[range] { line.replace_all(pattern, replacement); } } fn append(&mut self, index: usize, content: impl AsRef) { let lines_added = self.add_at(index + 1, content); self.current_line_number = usize::max(index + lines_added, self.lines.len()); } fn change(&mut self, range: Range, replacement: impl AsRef) { self.delete(range.clone()); self.insert(range.start, replacement); } fn delete(&mut self, range: Range) -> Drain<'_, LineBuffer> { self.current_line_number = usize::max(range.start, self.lines.len() - range.len()); self.lines.drain(range.clone()) } fn insert(&mut self, index: usize, content: impl AsRef) { let lines_added = self.add_at(index, content); self.current_line_number = index + lines_added - 1; } fn join(&mut self, range: Range) { let deleted_lines = self.delete(range.clone()); let line = deleted_lines.fold( LineBuffer { labels: Vec::new(), content: String::new(), }, |mut acc, line| { acc.labels.extend(line.labels); acc.content.push_str(&line.content); acc }, ); self.lines.insert(range.start, line); if range.len() > 1 { self.current_line_number = range.start; } } fn bookmark(&mut self, index: usize, label: String) { self.lines[index].add_label(label); } fn copy(&mut self, range: Range, index: usize) { let new_lines = self.lines[range.clone()] .iter() .cloned() .map(|mut line| { line.labels.clear(); line }) .collect::>(); self.lines.splice(index..index, new_lines); self.current_line_number = index + range.len(); } fn move_lines(&mut self, range: Range, index: usize) { let lines = self.delete(range.clone()).collect::>(); self.lines.splice(index..index, lines); self.current_line_number = index + range.len(); } fn read_lines(&self, range: Range) -> impl Iterator { self.lines[range].iter().map(|line| line.content.as_str()) } } impl LineBuffer { fn new() -> Self { Self { labels: Vec::new(), content: String::new(), } } fn from_str(content: &str) -> impl Iterator { content.split("\n").map(|line| Self { labels: Vec::new(), content: line.into(), }) } fn add_label(&mut self, label: String) { self.labels.push(label) } fn replace_all(&mut self, pattern: &Regex, replacement: &str) { self.content = pattern.replace_all(&self.content, replacement).into_owned() } } enum LineIndex { Current, Last, Number(u32), CurrentPlus(i32), Bookmark(String), FirstRegex(Regex), LastRegex(Regex), } struct LineRange { start: LineIndex, end: LineIndex, } enum Command { Append { position: LineIndex, content: String, buffer: Option, }, Change { range: LineRange, content: String, buffer: Option, }, Delete { range: LineRange, buffer: Option, }, Open { file: Box, }, New {}, SetFile { file: Box, buffer: Option, }, Find { range: LineRange, regex: Regex, buffer: Option, }, Insert { position: LineIndex, content: String, buffer: Option, }, Join { range: LineRange, buffer: Option, }, Bookmark { position: LineIndex, name: String, buffer: Option, }, Move { range: LineRange, position: LineIndex, buffer: Option, }, Read { range: LineRange, buffer: Option, }, Substitute { range: LineRange, regex: Regex, content: String, buffer: Option, }, Copy { range: LineRange, position: LineIndex, buffer: Option, }, Undo { buffer: Option, }, Write { buffer: Option, }, Close { buffer: Option, }, CurrentLineNumber { buffer: Option, }, Jump { position: LineIndex, buffer: Option, }, } fn parse_line_index(string: &str) -> Result> { if string == "." { Ok(LineIndex::Current) } else if string == "$" { Ok(LineIndex::Last) } else if string.starts_with("+") || string.starts_with("-") { Ok(LineIndex::CurrentPlus( string.parse::().map_err(|e| e.to_string())?, )) } else if let Ok(number) = string.parse::() { Ok(LineIndex::Number(number)) } else if let Some(bookmark) = string.strip_prefix("'") { Ok(LineIndex::Bookmark(bookmark.to_string())) } else if let Some(regex) = string.strip_prefix("/") { Ok(LineIndex::FirstRegex(Regex::new(regex)?)) } else if let Some(regex) = string.strip_prefix("?") { Ok(LineIndex::LastRegex(Regex::new(regex)?)) } else { Err("invalid line".into()) } } fn expect_field( message: &mut impl Iterator, error: &str, ) -> Result> { match message.next() { Some(field) => Ok(field), None => Err(error.into()), } } fn field_to_string(field: &MessageField) -> Result<&str, Rc> { let Some(field) = field.string() else { return Err("command must be a string".into()); }; match field { Ok(field) => Ok(field), Err(error) => Err(error.to_string().into()), } } fn expect_position(message: &mut impl Iterator) -> Result> { parse_line_index(field_to_string(&expect_field( message, "expected a line specifier", )?)?) } fn expect_range(message: &mut impl Iterator) -> Result> { Ok(LineRange { start: expect_position(message)?, end: expect_position(message)?, }) } fn expect_content(message: &mut impl Iterator) -> Result> { Ok(field_to_string(&expect_field(message, "expected a string argument")?)?.to_string()) } fn expect_file( message: &mut impl Iterator, ) -> Result, Rc> { let file = expect_field(message, "expected a file")?; match file { MessageField::File(file) => Ok(file), _ => Err("expected a file".into()), } } fn expect_regex(message: &mut impl Iterator) -> Result> { let regex = expect_content(message)?; Regex::new(®ex) } fn expect_buffer( message: &mut impl Iterator, ) -> Result, Rc> { message .next() .map(|field| { field_to_string(&field)? .parse::() .map_err(|e| e.to_string().into()) }) .transpose() } fn parse_command(mut message: impl Iterator) -> Result> { let command = expect_field(&mut message, "no command provided")?; let command = field_to_string(&command)?; if command == "append" { let position = expect_position(&mut message)?; let content = expect_content(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Append { position, content, buffer, }) } else if command == "change" { let range = expect_range(&mut message)?; let content = expect_content(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Change { range, content, buffer, }) } else if command == "delete" { let range = expect_range(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Delete { range, buffer }) } else if command == "open" { let file = expect_file(&mut message)?; Ok(Command::Open { file }) } else if command == "new" { Ok(Command::New {}) } else if command == "setfile" { let file = expect_file(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::SetFile { file, buffer }) } else if command == "find" { let range = expect_range(&mut message)?; let regex = expect_regex(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Find { range, regex, buffer, }) } else if command == "insert" { let position = expect_position(&mut message)?; let content = expect_content(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Insert { position, content, buffer, }) } else if command == "join" { let range = expect_range(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Join { range, buffer }) } else if command == "bookmark" { let position = expect_position(&mut message)?; let name = expect_content(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Bookmark { position, name, buffer, }) } else if command == "move" { let range = expect_range(&mut message)?; let position = expect_position(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Move { range, position, buffer, }) } else if command == "read" { let range = expect_range(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Read { range, buffer }) } else if command == "substitute" { let range = expect_range(&mut message)?; let regex = expect_regex(&mut message)?; let content = expect_content(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Substitute { range, regex, content, buffer, }) } else if command == "copy" { let range = expect_range(&mut message)?; let position = expect_position(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Copy { range, position, buffer, }) } else if command == "undo" { let buffer = expect_buffer(&mut message)?; Ok(Command::Undo { buffer }) } else if command == "write" { let buffer = expect_buffer(&mut message)?; Ok(Command::Write { buffer }) } else if command == "close" { let buffer = expect_buffer(&mut message)?; Ok(Command::Close { buffer }) } else if command == "linenumber" { let buffer = expect_buffer(&mut message)?; Ok(Command::CurrentLineNumber { buffer }) } else if command == "jump" { let position = expect_position(&mut message)?; let buffer = expect_buffer(&mut message)?; Ok(Command::Jump { position, buffer }) } else { Err(format!("{command} is not a valid command").into()) } } enum CommandResponse { Content(Box<[String]>), Number(usize), LineNumbers(Box<[usize]>), Empty, } fn run_command( command: Command, program_state: &mut ProgramState, ) -> Result> { match command { Command::Append { position, content, buffer, } => { let buffer = program_state.buffer_mut(buffer)?; let position = buffer.get_line_index(&position)?; buffer.append(position, content); Ok(CommandResponse::Empty) } Command::Change { range, content, buffer, } => { let buffer = program_state.buffer_mut(buffer)?; let range = buffer.get_line_range(&range)?; buffer.change(range, content); Ok(CommandResponse::Empty) } Command::Delete { range, buffer } => { let buffer = program_state.buffer_mut(buffer)?; let range = buffer.get_line_range(&range)?; buffer.delete(range); Ok(CommandResponse::Empty) } Command::Open { file } => { let id = program_state.next_buffer_id(); let buffer = FileBuffer::open(file).map_err(|e| e.to_string())?; program_state.open_files.insert(id, buffer); Ok(CommandResponse::Number(id)) } Command::New {} => { let id = program_state.next_buffer_id(); let buffer = FileBuffer::new(); program_state.open_files.insert(id, buffer); Ok(CommandResponse::Number(id)) } Command::SetFile { file, buffer } => { let buffer = program_state.buffer_mut(buffer)?; buffer.set_file(file); Ok(CommandResponse::Empty) } Command::Find { range, regex, buffer, } => { let buffer = program_state.buffer_mut(buffer)?; let range = buffer.get_line_range(&range)?; let matches = buffer.find(range, ®ex); Ok(CommandResponse::LineNumbers(matches.collect())) } Command::Insert { position, content, buffer, } => { let buffer = program_state.buffer_mut(buffer)?; let position = buffer.get_line_index(&position)?; buffer.insert(position, content); Ok(CommandResponse::Empty) } Command::Join { range, buffer } => { let buffer = program_state.buffer_mut(buffer)?; let range = buffer.get_line_range(&range)?; buffer.join(range); Ok(CommandResponse::Empty) } Command::Bookmark { position, name, buffer, } => { let buffer = program_state.buffer_mut(buffer)?; let position = buffer.get_line_index(&position)?; buffer.bookmark(position, name); Ok(CommandResponse::Empty) } Command::Move { range, position, buffer, } => { let buffer = program_state.buffer_mut(buffer)?; let range = buffer.get_line_range(&range)?; let position = buffer.get_line_index(&position)?; if range.contains(&position) { return Err("the range of moved lines contains the new position".into()); } buffer.move_lines(range, position); Ok(CommandResponse::Empty) } Command::Read { range, buffer } => { let buffer = program_state.buffer_mut(buffer)?; let range = buffer.get_line_range(&range)?; Ok(CommandResponse::Content( buffer.read_lines(range).map(ToOwned::to_owned).collect(), )) } Command::Substitute { range, regex, content, buffer, } => { let buffer = program_state.buffer_mut(buffer)?; let range = buffer.get_line_range(&range)?; buffer.replace_all(range, ®ex, &content); Ok(CommandResponse::Empty) } Command::Copy { range, position, buffer, } => { let buffer = program_state.buffer_mut(buffer)?; let range = buffer.get_line_range(&range)?; let position = buffer.get_line_index(&position)?; buffer.copy(range, position); Ok(CommandResponse::Empty) } Command::Undo { .. } => Err("Undo is not yet supported".into()), Command::Write { buffer } => { let buffer = program_state.buffer_mut(buffer)?; buffer.write().map_err(|e| e.to_string())?; Ok(CommandResponse::Empty) } Command::Close { buffer } => { program_state.close_buffer(buffer)?; Ok(CommandResponse::Empty) } Command::CurrentLineNumber { buffer } => { let buffer = program_state.buffer_mut(buffer)?; Ok(CommandResponse::Number(buffer.current_line_number())) } Command::Jump { position, buffer } => { let buffer = program_state.buffer_mut(buffer)?; let position = buffer.get_line_index(&position)?; buffer.set_current_line(position); Ok(CommandResponse::Empty) } } } pub fn dit(mut key: ThreadKey, channel: Receiver) { for message in channel { let sending_program = message.sending_program; let message_fields = message.fields.into_iter(); let return_space = message.return_space; let mut buffers = OPEN_BUFFERS.write().unwrap(); let program_state = buffers.entry(sending_program).or_default(); let command = parse_command(message_fields); let response = command.and_then(|command| run_command(command, program_state)); match response { Ok(CommandResponse::Empty) => return_space.respond_ok(MessageField::Empty), Ok(CommandResponse::Content(string)) => { return_space.respond_ok(MessageField::from(string.join("\n").as_str())) } Ok(CommandResponse::Number(number)) => { return_space.respond_ok(MessageField::from(number.to_string().as_str())); } Ok(CommandResponse::LineNumbers(numbers)) => { return_space.respond_ok(MessageField::from( numbers .iter() .fold(String::new(), |mut output, num| { output.push_str(&num.to_string()); output.push(','); output }) .as_str(), )) } Err(error) => return_space.respond_err(NonZeroU8::MIN, MessageField::from(&*error)), } } }