use anyhow::anyhow;use collections::{HashMap, HashSet};use futures::{ channel::{mpsc, oneshot}, pin_mut, SinkExt, StreamExt,};use gpui::{AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};use mlua::{ExternalResult, Lua, MultiValue, Table, UserData, UserDataMethods};use parking_lot::Mutex;use project::{search::SearchQuery, Fs, Project};use regex::Regex;use std::{ cell::RefCell, path::{Path, PathBuf}, sync::Arc,};use util::{paths::PathMatcher, ResultExt}; use crate::{SCRIPT_END_TAG, SCRIPT_START_TAG}; struct ForegroundFn(Box<dyn FnOnce(WeakEntity<ScriptSession>, AsyncApp) + Send>); pub struct ScriptSession { project: Entity<Project>, // TODO Remove this fs_changes: Arc<Mutex<HashMap<PathBuf, Vec<u8>>>>, foreground_fns_tx: mpsc::Sender<ForegroundFn>, _invoke_foreground_fns: Task<()>, scripts: Vec<Script>,} impl ScriptSession { pub fn new(project: Entity<Project>, cx: &mut Context<Self>) -> Self { let (foreground_fns_tx, mut foreground_fns_rx) = mpsc::channel(128); ScriptSession { project, fs_changes: Arc::new(Mutex::new(HashMap::default())), foreground_fns_tx, _invoke_foreground_fns: cx.spawn(|this, cx| async move { while let Some(foreground_fn) = foreground_fns_rx.next().await { foreground_fn.0(this.clone(), cx.clone()); } }), scripts: Vec::new(), } } pub fn new_script(&mut self) -> ScriptId { let id = ScriptId(self.scripts.len() as u32); let script = Script { id, state: ScriptState::Generating, source: SharedString::new_static(""), }; self.scripts.push(script); id } pub fn run_script( &mut self, script_id: ScriptId, script_src: String, cx: &mut Context<Self>, ) -> Task<anyhow::Result<()>> { let script = self.get_mut(script_id); let stdout = Arc::new(Mutex::new(String::new())); script.source = script_src.clone().into(); script.state = ScriptState::Running { stdout: stdout.clone(), }; let task = self.run_lua(script_src, stdout, cx); cx.emit(ScriptEvent::Spawned(script_id)); cx.spawn(|session, mut cx| async move { let result = task.await; session.update(&mut cx, |session, cx| { let script = session.get_mut(script_id); let stdout = script.stdout_snapshot(); script.state = match result { Ok(()) => ScriptState::Succeeded { stdout }, Err(error) => ScriptState::Failed { stdout, error }, }; cx.emit(ScriptEvent::Exited(script_id)) }) }) } fn run_lua( &mut self, script: String, stdout: Arc<Mutex<String>>, cx: &mut Context<Self>, ) -> Task<anyhow::Result<()>> { const SANDBOX_PREAMBLE: &str = include_str!("sandbox_preamble.lua"); // TODO Remove fs_changes let fs_changes = self.fs_changes.clone(); // TODO Honor all worktrees instead of the first one let root_dir = self .project .read(cx) .visible_worktrees(cx) .next() .map(|worktree| worktree.read(cx).abs_path()); let fs = self.project.read(cx).fs().clone(); let foreground_fns_tx = self.foreground_fns_tx.clone(); let task = cx.background_spawn({ let stdout = stdout.clone(); async move { let lua = Lua::new(); lua.set_memory_limit(2 * 1024 * 1024 * 1024)?; // 2 GB let globals = lua.globals(); globals.set( "sb_print", lua.create_function({ let stdout = stdout.clone(); move |_, args: MultiValue| Self::print(args, &stdout) })?, )?; globals.set( "search", lua.create_async_function({ let foreground_fns_tx = foreground_fns_tx.clone(); move |lua, regex| { let mut foreground_fns_tx = foreground_fns_tx.clone(); let fs = fs.clone(); async move { Self::search(&lua, &mut foreground_fns_tx, fs, regex) .await .into_lua_err() } } })?, )?; globals.set( "outline", lua.create_async_function({ let root_dir = root_dir.clone(); move |_lua, path| { let mut foreground_fns_tx = foreground_fns_tx.clone(); let root_dir = root_dir.clone(); async move { Self::outline(root_dir, &mut foreground_fns_tx, path) .await .into_lua_err() } } })?, )?; globals.set( "sb_io_open", lua.create_function({ let fs_changes = fs_changes.clone(); let root_dir = root_dir.clone(); move |lua, (path_str, mode)| { Self::io_open(&lua, &fs_changes, root_dir.as_ref(), path_str, mode) } })?, )?; globals.set("user_script", script)?; lua.load(SANDBOX_PREAMBLE).exec_async().await?; // Drop Lua instance to decrement reference count. drop(lua); anyhow::Ok(()) } }); task } pub fn get(&self, script_id: ScriptId) -> &Script { &self.scripts[script_id.0 as usize] } fn get_mut(&mut self, script_id: ScriptId) -> &mut Script { &mut self.scripts[script_id.0 as usize] } /// Sandboxed print() function in Lua. fn print(args: MultiValue, stdout: &Mutex<String>) -> mlua::Result<()> { for (index, arg) in args.into_iter().enumerate() { // Lua's `print()` prints tab characters between each argument. if index > 0 { stdout.lock().push('\t'); } // If the argument's to_string() fails, have the whole function call fail. stdout.lock().push_str(&arg.to_string()?); } stdout.lock().push('\n'); Ok(()) } /// Sandboxed io.open() function in Lua. fn io_open( lua: &Lua, fs_changes: &Arc<Mutex<HashMap<PathBuf, Vec<u8>>>>, root_dir: Option<&Arc<Path>>, path_str: String, mode: Option<String>, ) -> mlua::Result<(Option<Table>, String)> { let root_dir = root_dir .ok_or_else(|| mlua::Error::runtime("cannot open file without a root directory"))?; let mode = mode.unwrap_or_else(|| "r".to_string()); // Parse the mode string to determine read/write permissions let read_perm = mode.contains('r'); let write_perm = mode.contains('w') || mode.contains('a') || mode.contains('+'); let append = mode.contains('a'); let truncate = mode.contains('w'); // This will be the Lua value returned from the `open` function. let file = lua.create_table()?; // Store file metadata in the file file.set("__path", path_str.clone())?; file.set("__mode", mode.clone())?; file.set("__read_perm", read_perm)?; file.set("__write_perm", write_perm)?; let path = match Self::parse_abs_path_in_root_dir(&root_dir, &path_str) { Ok(path) => path, Err(err) => return Ok((None, format!("{err}"))), }; // close method let close_fn = { let fs_changes = fs_changes.clone(); lua.create_function(move |_lua, file_userdata: mlua::Table| { let write_perm = file_userdata.get::<bool>("__write_perm")?; let path = file_userdata.get::<String>("__path")?; if write_perm { // When closing a writable file, record the content let content = file_userdata.get::<mlua::AnyUserData>("__content")?; let content_ref = content.borrow::<FileContent>()?; let content_vec = content_ref.0.borrow(); // Don't actually write to disk; instead, just update fs_changes. let path_buf = PathBuf::from(&path); fs_changes .lock() .insert(path_buf.clone(), content_vec.clone()); } Ok(true) })? }; file.set("close", close_fn)?; // If it's a directory, give it a custom read() and return early. if path.is_dir() { // TODO handle the case where we changed it in the in-memory fs // Create a special directory handle file.set("__is_directory", true)?; // Store directory entries let entries = match std::fs::read_dir(&path) { Ok(entries) => { let mut entry_names = Vec::new(); for entry in entries.flatten() { entry_names.push(entry.file_name().to_string_lossy().into_owned()); } entry_names } Err(e) => return Ok((None, format!("Error reading directory: {}", e))), }; // Save the list of entries file.set("__dir_entries", entries)?; file.set("__dir_position", 0usize)?; // Create a directory-specific read function let read_fn = lua.create_function(|_lua, file_userdata: mlua::Table| { let position = file_userdata.get::<usize>("__dir_position")?; let entries = file_userdata.get::<Vec<String>>("__dir_entries")?; if position >= entries.len() { return Ok(None); // No more entries } let entry = entries[position].clone(); file_userdata.set("__dir_position", position + 1)?; Ok(Some(entry)) })?; file.set("read", read_fn)?; // If we got this far, the directory was opened successfully return Ok((Some(file), String::new())); } let fs_changes_map = fs_changes.lock(); let is_in_changes = fs_changes_map.contains_key(&path); let file_exists = is_in_changes || path.exists(); let mut file_content = Vec::new(); if file_exists && !truncate { if is_in_changes { file_content = fs_changes_map.get(&path).unwrap().clone(); } else { // Try to read existing content if file exists and we're not truncating match std::fs::read(&path) { Ok(content) => file_content = content, Err(e) => return Ok((None, format!("Error reading file: {}", e))), } } } drop(fs_changes_map); // Unlock the fs_changes mutex. // If in append mode, position should be at the end let position = if append && file_exists { file_content.len() } else { 0 }; file.set("__position", position)?; file.set( "__content", lua.create_userdata(FileContent(RefCell::new(file_content)))?, )?; // Create file methods // read method let read_fn = { lua.create_function( |_lua, (file_userdata, format): (mlua::Table, Option<mlua::Value>)| { let read_perm = file_userdata.get::<bool>("__read_perm")?; if !read_perm { return Err(mlua::Error::runtime("File not open for reading")); } let content = file_userdata.get::<mlua::AnyUserData>("__content")?; let mut position = file_userdata.get::<usize>("__position")?; let content_ref = content.borrow::<FileContent>()?; let content_vec = content_ref.0.borrow(); if position >= content_vec.len() { return Ok(None); // EOF } match format { Some(mlua::Value::String(s)) => { let lossy_string = s.to_string_lossy(); let format_str: &str = lossy_string.as_ref(); // Only consider the first 2 bytes, since it's common to pass e.g. "*all" instead of "*a" match &format_str[0..2] { "*a" => { // Read entire file from current position let result = String::from_utf8_lossy(&content_vec[position..]) .to_string(); position = content_vec.len(); file_userdata.set("__position", position)?; Ok(Some(result)) } "*l" => { // Read next line let mut line = Vec::new(); let mut found_newline = false; while position < content_vec.len() { let byte = content_vec[position]; position += 1; if byte == b'\n' { found_newline = true; break; } // Skip \r in \r\n sequence but add it if it's alone if byte == b'\r' { if position < content_vec.len() && content_vec[position] == b'\n' { position += 1; found_newline = true; break; } } line.push(byte); } file_userdata.set("__position", position)?; if !found_newline && line.is_empty() && position >= content_vec.len() { return Ok(None); // EOF } let result = String::from_utf8_lossy(&line).to_string(); Ok(Some(result)) } "*n" => { // Try to parse as a number (number of bytes to read) match format_str.parse::<usize>() { Ok(n) => { let end = std::cmp::min(position + n, content_vec.len()); let bytes = &content_vec[position..end]; let result = String::from_utf8_lossy(bytes).to_string(); position = end; file_userdata.set("__position", position)?; Ok(Some(result)) } Err(_) => Err(mlua::Error::runtime(format!( "Invalid format: {}", format_str ))), } } "*L" => { // Read next line keeping the end of line let mut line = Vec::new(); while position < content_vec.len() { let byte = content_vec[position]; position += 1; line.push(byte); if byte == b'\n' { break; } // If we encounter a \r, add it and check if the next is \n if byte == b'\r' && position < content_vec.len() && content_vec[position] == b'\n' { line.push(content_vec[position]); position += 1; break; } } file_userdata.set("__position", position)?; if line.is_empty() && position >= content_vec.len() { return Ok(None); // EOF } let result = String::from_utf8_lossy(&line).to_string(); Ok(Some(result)) } _ => Err(mlua::Error::runtime(format!( "Unsupported format: {}", format_str ))), } } Some(mlua::Value::Number(n)) => { // Read n bytes let n = n as usize; let end = std::cmp::min(position + n, content_vec.len()); let bytes = &content_vec[position..end]; let result = String::from_utf8_lossy(bytes).to_string(); position = end; file_userdata.set("__position", position)?; Ok(Some(result)) } Some(_) => Err(mlua::Error::runtime("Invalid format")), None => { // Default is to read a line let mut line = Vec::new(); let mut found_newline = false; while position < content_vec.len() { let byte = content_vec[position]; position += 1; if byte == b'\n' { found_newline = true; break; } // Handle \r\n if byte == b'\r' { if position < content_vec.len()Lines 1–500 of 945. Load more
use anyhow::anyhow; use collections::{HashMap, HashSet}; use futures::{ channel::{mpsc, oneshot}, pin_mut, SinkExt, StreamExt, }; use gpui::{AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity}; use mlua::{ExternalResult, Lua, MultiValue, Table, UserData, UserDataMethods}; use parking_lot::Mutex; use project::{search::SearchQuery, Fs, Project}; use regex::Regex; use std::{ cell::RefCell, path::{Path, PathBuf}, sync::Arc, }; use util::{paths::PathMatcher, ResultExt}; use crate::{SCRIPT_END_TAG, SCRIPT_START_TAG}; struct ForegroundFn(Box<dyn FnOnce(WeakEntity<ScriptSession>, AsyncApp) + Send>); pub struct ScriptSession { project: Entity<Project>, // TODO Remove this fs_changes: Arc<Mutex<HashMap<PathBuf, Vec<u8>>>>, foreground_fns_tx: mpsc::Sender<ForegroundFn>, _invoke_foreground_fns: Task<()>, scripts: Vec<Script>, } impl ScriptSession { pub fn new(project: Entity<Project>, cx: &mut Context<Self>) -> Self { let (foreground_fns_tx, mut foreground_fns_rx) = mpsc::channel(128); ScriptSession { project, fs_changes: Arc::new(Mutex::new(HashMap::default())), foreground_fns_tx, _invoke_foreground_fns: cx.spawn(|this, cx| async move { while let Some(foreground_fn) = foreground_fns_rx.next().await { foreground_fn.0(this.clone(), cx.clone()); } }), scripts: Vec::new(), } } pub fn new_script(&mut self) -> ScriptId { let id = ScriptId(self.scripts.len() as u32); let script = Script { id, state: ScriptState::Generating, source: SharedString::new_static(""), }; self.scripts.push(script); id } pub fn run_script( &mut self, script_id: ScriptId, script_src: String, cx: &mut Context<Self>, ) -> Task<anyhow::Result<()>> { let script = self.get_mut(script_id); let stdout = Arc::new(Mutex::new(String::new())); script.source = script_src.clone().into(); script.state = ScriptState::Running { stdout: stdout.clone(), }; let task = self.run_lua(script_src, stdout, cx); cx.emit(ScriptEvent::Spawned(script_id)); cx.spawn(|session, mut cx| async move { let result = task.await; session.update(&mut cx, |session, cx| { let script = session.get_mut(script_id); let stdout = script.stdout_snapshot(); script.state = match result { Ok(()) => ScriptState::Succeeded { stdout }, Err(error) => ScriptState::Failed { stdout, error }, }; cx.emit(ScriptEvent::Exited(script_id)) }) }) } fn run_lua( &mut self, script: String, stdout: Arc<Mutex<String>>, cx: &mut Context<Self>, ) -> Task<anyhow::Result<()>> { const SANDBOX_PREAMBLE: &str = include_str!("sandbox_preamble.lua"); // TODO Remove fs_changes let fs_changes = self.fs_changes.clone(); // TODO Honor all worktrees instead of the first one let root_dir = self .project .read(cx) .visible_worktrees(cx) .next() .map(|worktree| worktree.read(cx).abs_path()); let fs = self.project.read(cx).fs().clone(); let foreground_fns_tx = self.foreground_fns_tx.clone(); let task = cx.background_spawn({ let stdout = stdout.clone(); async move { let lua = Lua::new(); lua.set_memory_limit(2 * 1024 * 1024 * 1024)?; // 2 GB let globals = lua.globals(); globals.set( "sb_print", lua.create_function({ let stdout = stdout.clone(); move |_, args: MultiValue| Self::print(args, &stdout) })?, )?; globals.set( "search", lua.create_async_function({ let foreground_fns_tx = foreground_fns_tx.clone(); move |lua, regex| { let mut foreground_fns_tx = foreground_fns_tx.clone(); let fs = fs.clone(); async move { Self::search(&lua, &mut foreground_fns_tx, fs, regex) .await .into_lua_err() } } })?, )?; globals.set( "outline", lua.create_async_function({ let root_dir = root_dir.clone(); move |_lua, path| { let mut foreground_fns_tx = foreground_fns_tx.clone(); let root_dir = root_dir.clone(); async move { Self::outline(root_dir, &mut foreground_fns_tx, path) .await .into_lua_err() } } })?, )?; globals.set( "sb_io_open", lua.create_function({ let fs_changes = fs_changes.clone(); let root_dir = root_dir.clone(); move |lua, (path_str, mode)| { Self::io_open(&lua, &fs_changes, root_dir.as_ref(), path_str, mode) } })?, )?; globals.set("user_script", script)?; lua.load(SANDBOX_PREAMBLE).exec_async().await?; // Drop Lua instance to decrement reference count. drop(lua); anyhow::Ok(()) } }); task } pub fn get(&self, script_id: ScriptId) -> &Script { &self.scripts[script_id.0 as usize] } fn get_mut(&mut self, script_id: ScriptId) -> &mut Script { &mut self.scripts[script_id.0 as usize] } /// Sandboxed print() function in Lua. fn print(args: MultiValue, stdout: &Mutex<String>) -> mlua::Result<()> { for (index, arg) in args.into_iter().enumerate() { // Lua's `print()` prints tab characters between each argument. if index > 0 { stdout.lock().push('\t'); } // If the argument's to_string() fails, have the whole function call fail. stdout.lock().push_str(&arg.to_string()?); } stdout.lock().push('\n'); Ok(()) } /// Sandboxed io.open() function in Lua. fn io_open( lua: &Lua, fs_changes: &Arc<Mutex<HashMap<PathBuf, Vec<u8>>>>, root_dir: Option<&Arc<Path>>, path_str: String, mode: Option<String>, ) -> mlua::Result<(Option<Table>, String)> { let root_dir = root_dir .ok_or_else(|| mlua::Error::runtime("cannot open file without a root directory"))?; let mode = mode.unwrap_or_else(|| "r".to_string()); // Parse the mode string to determine read/write permissions let read_perm = mode.contains('r'); let write_perm = mode.contains('w') || mode.contains('a') || mode.contains('+'); let append = mode.contains('a'); let truncate = mode.contains('w'); // This will be the Lua value returned from the `open` function. let file = lua.create_table()?; // Store file metadata in the file file.set("__path", path_str.clone())?; file.set("__mode", mode.clone())?; file.set("__read_perm", read_perm)?; file.set("__write_perm", write_perm)?; let path = match Self::parse_abs_path_in_root_dir(&root_dir, &path_str) { Ok(path) => path, Err(err) => return Ok((None, format!("{err}"))), }; // close method let close_fn = { let fs_changes = fs_changes.clone(); lua.create_function(move |_lua, file_userdata: mlua::Table| { let write_perm = file_userdata.get::<bool>("__write_perm")?; let path = file_userdata.get::<String>("__path")?; if write_perm { // When closing a writable file, record the content let content = file_userdata.get::<mlua::AnyUserData>("__content")?; let content_ref = content.borrow::<FileContent>()?; let content_vec = content_ref.0.borrow(); // Don't actually write to disk; instead, just update fs_changes. let path_buf = PathBuf::from(&path); fs_changes .lock() .insert(path_buf.clone(), content_vec.clone()); } Ok(true) })? }; file.set("close", close_fn)?; // If it's a directory, give it a custom read() and return early. if path.is_dir() { // TODO handle the case where we changed it in the in-memory fs // Create a special directory handle file.set("__is_directory", true)?; // Store directory entries let entries = match std::fs::read_dir(&path) { Ok(entries) => { let mut entry_names = Vec::new(); for entry in entries.flatten() { entry_names.push(entry.file_name().to_string_lossy().into_owned()); } entry_names } Err(e) => return Ok((None, format!("Error reading directory: {}", e))), }; // Save the list of entries file.set("__dir_entries", entries)?; file.set("__dir_position", 0usize)?; // Create a directory-specific read function let read_fn = lua.create_function(|_lua, file_userdata: mlua::Table| { let position = file_userdata.get::<usize>("__dir_position")?; let entries = file_userdata.get::<Vec<String>>("__dir_entries")?; if position >= entries.len() { return Ok(None); // No more entries } let entry = entries[position].clone(); file_userdata.set("__dir_position", position + 1)?; Ok(Some(entry)) })?; file.set("read", read_fn)?; // If we got this far, the directory was opened successfully return Ok((Some(file), String::new())); } let fs_changes_map = fs_changes.lock(); let is_in_changes = fs_changes_map.contains_key(&path); let file_exists = is_in_changes || path.exists(); let mut file_content = Vec::new(); if file_exists && !truncate { if is_in_changes { file_content = fs_changes_map.get(&path).unwrap().clone(); } else { // Try to read existing content if file exists and we're not truncating match std::fs::read(&path) { Ok(content) => file_content = content, Err(e) => return Ok((None, format!("Error reading file: {}", e))), } } } drop(fs_changes_map); // Unlock the fs_changes mutex. // If in append mode, position should be at the end let position = if append && file_exists { file_content.len() } else { 0 }; file.set("__position", position)?; file.set( "__content", lua.create_userdata(FileContent(RefCell::new(file_content)))?, )?; // Create file methods // read method let read_fn = { lua.create_function( |_lua, (file_userdata, format): (mlua::Table, Option<mlua::Value>)| { let read_perm = file_userdata.get::<bool>("__read_perm")?; if !read_perm { return Err(mlua::Error::runtime("File not open for reading")); } let content = file_userdata.get::<mlua::AnyUserData>("__content")?; let mut position = file_userdata.get::<usize>("__position")?; let content_ref = content.borrow::<FileContent>()?; let content_vec = content_ref.0.borrow(); if position >= content_vec.len() { return Ok(None); // EOF } match format { Some(mlua::Value::String(s)) => { let lossy_string = s.to_string_lossy(); let format_str: &str = lossy_string.as_ref(); // Only consider the first 2 bytes, since it's common to pass e.g. "*all" instead of "*a" match &format_str[0..2] { "*a" => { // Read entire file from current position let result = String::from_utf8_lossy(&content_vec[position..]) .to_string(); position = content_vec.len(); file_userdata.set("__position", position)?; Ok(Some(result)) } "*l" => { // Read next line let mut line = Vec::new(); let mut found_newline = false; while position < content_vec.len() { let byte = content_vec[position]; position += 1; if byte == b'\n' { found_newline = true; break; } // Skip \r in \r\n sequence but add it if it's alone if byte == b'\r' { if position < content_vec.len() && content_vec[position] == b'\n' { position += 1; found_newline = true; break; } } line.push(byte); } file_userdata.set("__position", position)?; if !found_newline && line.is_empty() && position >= content_vec.len() { return Ok(None); // EOF } let result = String::from_utf8_lossy(&line).to_string(); Ok(Some(result)) } "*n" => { // Try to parse as a number (number of bytes to read) match format_str.parse::<usize>() { Ok(n) => { let end = std::cmp::min(position + n, content_vec.len()); let bytes = &content_vec[position..end]; let result = String::from_utf8_lossy(bytes).to_string(); position = end; file_userdata.set("__position", position)?; Ok(Some(result)) } Err(_) => Err(mlua::Error::runtime(format!( "Invalid format: {}", format_str ))), } } "*L" => { // Read next line keeping the end of line let mut line = Vec::new(); while position < content_vec.len() { let byte = content_vec[position]; position += 1; line.push(byte); if byte == b'\n' { break; } // If we encounter a \r, add it and check if the next is \n if byte == b'\r' && position < content_vec.len() && content_vec[position] == b'\n' { line.push(content_vec[position]); position += 1; break; } } file_userdata.set("__position", position)?; if line.is_empty() && position >= content_vec.len() { return Ok(None); // EOF } let result = String::from_utf8_lossy(&line).to_string(); Ok(Some(result)) } _ => Err(mlua::Error::runtime(format!( "Unsupported format: {}", format_str ))), } } Some(mlua::Value::Number(n)) => { // Read n bytes let n = n as usize; let end = std::cmp::min(position + n, content_vec.len()); let bytes = &content_vec[position..end]; let result = String::from_utf8_lossy(bytes).to_string(); position = end; file_userdata.set("__position", position)?; Ok(Some(result)) } Some(_) => Err(mlua::Error::runtime("Invalid format")), None => { // Default is to read a line let mut line = Vec::new(); let mut found_newline = false; while position < content_vec.len() { let byte = content_vec[position]; position += 1; if byte == b'\n' { found_newline = true; break; } // Handle \r\n if byte == b'\r' { if position < content_vec.len()