use anyhow::anyhow;use collections::HashSet;use futures::{ channel::{mpsc, oneshot}, pin_mut, SinkExt, StreamExt,};use gpui::{AppContext, AsyncApp, Context, Entity, Task, WeakEntity};use language::Buffer;use mlua::{ExternalResult, Lua, MultiValue, Table, UserData, UserDataMethods};use parking_lot::Mutex;use project::{search::SearchQuery, Fs, Project, ProjectPath, WorktreeId};use regex::Regex;use std::{ path::{Path, PathBuf}, sync::Arc,};use util::{paths::PathMatcher, ResultExt}; struct ForegroundFn(Box<dyn FnOnce(WeakEntity<ScriptingSession>, AsyncApp) + Send>); pub struct ScriptingSession { project: Entity<Project>, scripts: Vec<Script>, changed_buffers: HashSet<Entity<Buffer>>, foreground_fns_tx: mpsc::Sender<ForegroundFn>, _invoke_foreground_fns: Task<()>,} impl ScriptingSession { pub fn new(project: Entity<Project>, cx: &mut Context<Self>) -> Self { let (foreground_fns_tx, mut foreground_fns_rx) = mpsc::channel(128); ScriptingSession { project, scripts: Vec::new(), changed_buffers: HashSet::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()); } }), } } pub fn changed_buffers(&self) -> impl ExactSizeIterator<Item = &Entity<Buffer>> { self.changed_buffers.iter() } pub fn run_script( &mut self, script_src: String, cx: &mut Context<Self>, ) -> (ScriptId, Task<()>) { let id = ScriptId(self.scripts.len() as u32); let stdout = Arc::new(Mutex::new(String::new())); let script = Script { state: ScriptState::Running { stdout: stdout.clone(), }, }; self.scripts.push(script); let task = self.run_lua(script_src, stdout, cx); let task = cx.spawn(|session, mut cx| async move { let result = task.await; session .update(&mut cx, |session, _cx| { let script = session.get_mut(id); let stdout = script.stdout_snapshot(); script.state = match result { Ok(()) => ScriptState::Succeeded { stdout }, Err(error) => ScriptState::Failed { stdout, error }, }; }) .log_err(); }); (id, task) } 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 Honor all worktrees instead of the first one let worktree_info = self .project .read(cx) .visible_worktrees(cx) .next() .map(|worktree| { let worktree = worktree.read(cx); (worktree.id(), worktree.abs_path()) }); let root_dir = worktree_info.as_ref().map(|(_, root)| root.clone()); 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(); // Use the project root dir as the script's current working dir. if let Some(root_dir) = &root_dir { if let Some(root_dir) = root_dir.to_str() { globals.set("cwd", root_dir)?; } } 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(); let fs = fs.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(); let foreground_fns_tx = foreground_fns_tx.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_async_function({ let worktree_info = worktree_info.clone(); let foreground_fns_tx = foreground_fns_tx.clone(); move |lua, (path_str, mode)| { let worktree_info = worktree_info.clone(); let mut foreground_fns_tx = foreground_fns_tx.clone(); let fs = fs.clone(); async move { Self::io_open( &lua, worktree_info, &mut foreground_fns_tx, fs, path_str, mode, ) .await } } })?, )?; 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. async fn io_open( lua: &Lua, worktree_info: Option<(WorktreeId, Arc<Path>)>, foreground_tx: &mut mpsc::Sender<ForegroundFn>, fs: Arc<dyn Fs>, path_str: String, mode: Option<String>, ) -> mlua::Result<(Option<Table>, String)> { let (worktree_id, root_dir) = worktree_info .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("__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}"))), }; let project_path = ProjectPath { worktree_id, path: Path::new(&path_str).into(), }; // flush / close method let flush_fn = { let project_path = project_path.clone(); let foreground_tx = foreground_tx.clone(); lua.create_async_function(move |_lua, file_userdata: mlua::Table| { let project_path = project_path.clone(); let mut foreground_tx = foreground_tx.clone(); async move { Self::io_file_flush(file_userdata, project_path, &mut foreground_tx).await } })? }; file.set("flush", flush_fn.clone())?; // We don't really hold files open, so we only need to flush on close file.set("close", flush_fn)?; // If it's a directory, give it a custom read() and return early. if fs.is_dir(&path).await { return Self::io_file_dir(lua, fs, file, &path).await; } let mut file_content = Vec::new(); if !truncate { // Try to read existing content if we're not truncating match Self::read_buffer(project_path.clone(), foreground_tx).await { Ok(content) => file_content = content.into_bytes(), Err(e) => return Ok((None, format!("Error reading file: {}", e))), } } // If in append mode, position should be at the end let position = if append { file_content.len() } else { 0 }; file.set("__position", position)?; file.set( "__content", lua.create_userdata(FileContent(Arc::new(Mutex::new(file_content))))?, )?; // Create file methods // read method let read_fn = lua.create_function(Self::io_file_read)?; file.set("read", read_fn)?; // write method let write_fn = lua.create_function(Self::io_file_write)?; file.set("write", write_fn)?; // If we got this far, the file was opened successfully Ok((Some(file), String::new())) } async fn read_buffer( project_path: ProjectPath, foreground_tx: &mut mpsc::Sender<ForegroundFn>, ) -> anyhow::Result<String> { Self::run_foreground_fn( "read file from buffer", foreground_tx, Box::new(move |session, mut cx| { session.update(&mut cx, |session, cx| { let open_buffer_task = session .project .update(cx, |project, cx| project.open_buffer(project_path, cx)); cx.spawn(|_, cx| async move { let buffer = open_buffer_task.await?; let text = buffer.read_with(&cx, |buffer, _cx| buffer.text())?; Ok(text) }) }) }), ) .await?? .await } async fn io_file_flush( file_userdata: mlua::Table, project_path: ProjectPath, foreground_tx: &mut mpsc::Sender<ForegroundFn>, ) -> mlua::Result<bool> { let write_perm = file_userdata.get::<bool>("__write_perm")?; if write_perm { let content = file_userdata.get::<mlua::AnyUserData>("__content")?; let content_ref = content.borrow::<FileContent>()?; let text = { let mut content_vec = content_ref.0.lock(); let content_vec = std::mem::take(&mut *content_vec); String::from_utf8(content_vec).into_lua_err()? }; Self::write_to_buffer(project_path, text, foreground_tx) .await .into_lua_err()?; } Ok(true) } async fn write_to_buffer( project_path: ProjectPath, text: String, foreground_tx: &mut mpsc::Sender<ForegroundFn>, ) -> anyhow::Result<()> { Self::run_foreground_fn( "write to buffer", foreground_tx, Box::new(move |session, mut cx| { session.update(&mut cx, |session, cx| { let open_buffer_task = session .project .update(cx, |project, cx| project.open_buffer(project_path, cx)); cx.spawn(move |session, mut cx| async move { let buffer = open_buffer_task.await?; let diff = buffer .update(&mut cx, |buffer, cx| buffer.diff(text, cx))? .await; buffer.update(&mut cx, |buffer, cx| { buffer.apply_diff(diff, cx); })?; session .update(&mut cx, { let buffer = buffer.clone(); |session, cx| { session .project .update(cx, |project, cx| project.save_buffer(buffer, cx)) } })? .await?; // If we saved successfully, mark buffer as changed session.update(&mut cx, |session, _cx| { session.changed_buffers.insert(buffer); }) }) }) }), ) .await?? .await } async fn io_file_dir( lua: &Lua, fs: Arc<dyn Fs>, file: Table, path: &Path, ) -> mlua::Result<(Option<Table>, String)> { // Create a special directory handle file.set("__is_directory", true)?; // Store directory entries let entries = match fs.read_dir(&path).await { Ok(entries) => { let mut entry_names = Vec::new(); // Process the stream of directory entries pin_mut!(entries); while let Some(Ok(entry_result)) = entries.next().await { if let Some(file_name) = entry_result.file_name() { entry_names.push(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())); } fn io_file_read( lua: &Lua, (file_userdata, format): (Table, Option<mlua::Value>), ) -> mlua::Result<Option<mlua::String>> { 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 position = file_userdata.get::<usize>("__position")?; let content_ref = content.borrow::<FileContent>()?; let content = content_ref.0.lock(); if position >= content.len() { return Ok(None); // EOF } let (result, new_position) = match Self::io_file_read_format(format)? { FileReadFormat::All => { // Read entire file from current position let result = content[position..].to_vec(); (Some(result), content.len()) } FileReadFormat::Line => { if let Some(next_newline_ix) = content[position..].iter().position(|c| *c == b'\n') { let mut line = content[position..position + next_newline_ix].to_vec(); if line.ends_with(b"\r") { line.pop(); } (Some(line), position + next_newline_ix + 1) } else if position < content.len() { let line = content[position..].to_vec(); (Some(line), content.len())Lines 1–500 of 1191. Load more
use anyhow::anyhow; use collections::HashSet; use futures::{ channel::{mpsc, oneshot}, pin_mut, SinkExt, StreamExt, }; use gpui::{AppContext, AsyncApp, Context, Entity, Task, WeakEntity}; use language::Buffer; use mlua::{ExternalResult, Lua, MultiValue, Table, UserData, UserDataMethods}; use parking_lot::Mutex; use project::{search::SearchQuery, Fs, Project, ProjectPath, WorktreeId}; use regex::Regex; use std::{ path::{Path, PathBuf}, sync::Arc, }; use util::{paths::PathMatcher, ResultExt}; struct ForegroundFn(Box<dyn FnOnce(WeakEntity<ScriptingSession>, AsyncApp) + Send>); pub struct ScriptingSession { project: Entity<Project>, scripts: Vec<Script>, changed_buffers: HashSet<Entity<Buffer>>, foreground_fns_tx: mpsc::Sender<ForegroundFn>, _invoke_foreground_fns: Task<()>, } impl ScriptingSession { pub fn new(project: Entity<Project>, cx: &mut Context<Self>) -> Self { let (foreground_fns_tx, mut foreground_fns_rx) = mpsc::channel(128); ScriptingSession { project, scripts: Vec::new(), changed_buffers: HashSet::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()); } }), } } pub fn changed_buffers(&self) -> impl ExactSizeIterator<Item = &Entity<Buffer>> { self.changed_buffers.iter() } pub fn run_script( &mut self, script_src: String, cx: &mut Context<Self>, ) -> (ScriptId, Task<()>) { let id = ScriptId(self.scripts.len() as u32); let stdout = Arc::new(Mutex::new(String::new())); let script = Script { state: ScriptState::Running { stdout: stdout.clone(), }, }; self.scripts.push(script); let task = self.run_lua(script_src, stdout, cx); let task = cx.spawn(|session, mut cx| async move { let result = task.await; session .update(&mut cx, |session, _cx| { let script = session.get_mut(id); let stdout = script.stdout_snapshot(); script.state = match result { Ok(()) => ScriptState::Succeeded { stdout }, Err(error) => ScriptState::Failed { stdout, error }, }; }) .log_err(); }); (id, task) } 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 Honor all worktrees instead of the first one let worktree_info = self .project .read(cx) .visible_worktrees(cx) .next() .map(|worktree| { let worktree = worktree.read(cx); (worktree.id(), worktree.abs_path()) }); let root_dir = worktree_info.as_ref().map(|(_, root)| root.clone()); 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(); // Use the project root dir as the script's current working dir. if let Some(root_dir) = &root_dir { if let Some(root_dir) = root_dir.to_str() { globals.set("cwd", root_dir)?; } } 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(); let fs = fs.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(); let foreground_fns_tx = foreground_fns_tx.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_async_function({ let worktree_info = worktree_info.clone(); let foreground_fns_tx = foreground_fns_tx.clone(); move |lua, (path_str, mode)| { let worktree_info = worktree_info.clone(); let mut foreground_fns_tx = foreground_fns_tx.clone(); let fs = fs.clone(); async move { Self::io_open( &lua, worktree_info, &mut foreground_fns_tx, fs, path_str, mode, ) .await } } })?, )?; 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. async fn io_open( lua: &Lua, worktree_info: Option<(WorktreeId, Arc<Path>)>, foreground_tx: &mut mpsc::Sender<ForegroundFn>, fs: Arc<dyn Fs>, path_str: String, mode: Option<String>, ) -> mlua::Result<(Option<Table>, String)> { let (worktree_id, root_dir) = worktree_info .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("__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}"))), }; let project_path = ProjectPath { worktree_id, path: Path::new(&path_str).into(), }; // flush / close method let flush_fn = { let project_path = project_path.clone(); let foreground_tx = foreground_tx.clone(); lua.create_async_function(move |_lua, file_userdata: mlua::Table| { let project_path = project_path.clone(); let mut foreground_tx = foreground_tx.clone(); async move { Self::io_file_flush(file_userdata, project_path, &mut foreground_tx).await } })? }; file.set("flush", flush_fn.clone())?; // We don't really hold files open, so we only need to flush on close file.set("close", flush_fn)?; // If it's a directory, give it a custom read() and return early. if fs.is_dir(&path).await { return Self::io_file_dir(lua, fs, file, &path).await; } let mut file_content = Vec::new(); if !truncate { // Try to read existing content if we're not truncating match Self::read_buffer(project_path.clone(), foreground_tx).await { Ok(content) => file_content = content.into_bytes(), Err(e) => return Ok((None, format!("Error reading file: {}", e))), } } // If in append mode, position should be at the end let position = if append { file_content.len() } else { 0 }; file.set("__position", position)?; file.set( "__content", lua.create_userdata(FileContent(Arc::new(Mutex::new(file_content))))?, )?; // Create file methods // read method let read_fn = lua.create_function(Self::io_file_read)?; file.set("read", read_fn)?; // write method let write_fn = lua.create_function(Self::io_file_write)?; file.set("write", write_fn)?; // If we got this far, the file was opened successfully Ok((Some(file), String::new())) } async fn read_buffer( project_path: ProjectPath, foreground_tx: &mut mpsc::Sender<ForegroundFn>, ) -> anyhow::Result<String> { Self::run_foreground_fn( "read file from buffer", foreground_tx, Box::new(move |session, mut cx| { session.update(&mut cx, |session, cx| { let open_buffer_task = session .project .update(cx, |project, cx| project.open_buffer(project_path, cx)); cx.spawn(|_, cx| async move { let buffer = open_buffer_task.await?; let text = buffer.read_with(&cx, |buffer, _cx| buffer.text())?; Ok(text) }) }) }), ) .await?? .await } async fn io_file_flush( file_userdata: mlua::Table, project_path: ProjectPath, foreground_tx: &mut mpsc::Sender<ForegroundFn>, ) -> mlua::Result<bool> { let write_perm = file_userdata.get::<bool>("__write_perm")?; if write_perm { let content = file_userdata.get::<mlua::AnyUserData>("__content")?; let content_ref = content.borrow::<FileContent>()?; let text = { let mut content_vec = content_ref.0.lock(); let content_vec = std::mem::take(&mut *content_vec); String::from_utf8(content_vec).into_lua_err()? }; Self::write_to_buffer(project_path, text, foreground_tx) .await .into_lua_err()?; } Ok(true) } async fn write_to_buffer( project_path: ProjectPath, text: String, foreground_tx: &mut mpsc::Sender<ForegroundFn>, ) -> anyhow::Result<()> { Self::run_foreground_fn( "write to buffer", foreground_tx, Box::new(move |session, mut cx| { session.update(&mut cx, |session, cx| { let open_buffer_task = session .project .update(cx, |project, cx| project.open_buffer(project_path, cx)); cx.spawn(move |session, mut cx| async move { let buffer = open_buffer_task.await?; let diff = buffer .update(&mut cx, |buffer, cx| buffer.diff(text, cx))? .await; buffer.update(&mut cx, |buffer, cx| { buffer.apply_diff(diff, cx); })?; session .update(&mut cx, { let buffer = buffer.clone(); |session, cx| { session .project .update(cx, |project, cx| project.save_buffer(buffer, cx)) } })? .await?; // If we saved successfully, mark buffer as changed session.update(&mut cx, |session, _cx| { session.changed_buffers.insert(buffer); }) }) }) }), ) .await?? .await } async fn io_file_dir( lua: &Lua, fs: Arc<dyn Fs>, file: Table, path: &Path, ) -> mlua::Result<(Option<Table>, String)> { // Create a special directory handle file.set("__is_directory", true)?; // Store directory entries let entries = match fs.read_dir(&path).await { Ok(entries) => { let mut entry_names = Vec::new(); // Process the stream of directory entries pin_mut!(entries); while let Some(Ok(entry_result)) = entries.next().await { if let Some(file_name) = entry_result.file_name() { entry_names.push(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())); } fn io_file_read( lua: &Lua, (file_userdata, format): (Table, Option<mlua::Value>), ) -> mlua::Result<Option<mlua::String>> { 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 position = file_userdata.get::<usize>("__position")?; let content_ref = content.borrow::<FileContent>()?; let content = content_ref.0.lock(); if position >= content.len() { return Ok(None); // EOF } let (result, new_position) = match Self::io_file_read_format(format)? { FileReadFormat::All => { // Read entire file from current position let result = content[position..].to_vec(); (Some(result), content.len()) } FileReadFormat::Line => { if let Some(next_newline_ix) = content[position..].iter().position(|c| *c == b'\n') { let mut line = content[position..position + next_newline_ix].to_vec(); if line.ends_with(b"\r") { line.pop(); } (Some(line), position + next_newline_ix + 1) } else if position < content.len() { let line = content[position..].to_vec(); (Some(line), content.len())