use std::path::Path;
use {
reovim_driver_codec::{CodecSessionState, ContentCodecFactoryStore},
reovim_driver_command::{
ArgKind, ArgSpec, Command, CommandContext, CommandHandler, CommandResult, RuntimeSignal,
},
reovim_driver_session::{BufferApi, CommandApi, ExtensionApi, SessionRuntime},
reovim_kernel::api::v1::{
CommandId, ModuleId,
events::kernel::{BufferSaved, BufferWillSave},
},
};
const COMMANDS_MODULE: ModuleId = ModuleId::new("commands");
#[derive(Debug, Clone, Copy)]
pub struct WriteCommand;
pub const WRITE_CMD_ID: CommandId = CommandId::new(COMMANDS_MODULE, "write");
impl Command for WriteCommand {
fn id(&self) -> CommandId {
WRITE_CMD_ID
}
fn description(&self) -> &'static str {
"Write the current buffer to disk. Use :w filename to save to a specific file."
}
fn args(&self) -> Vec<ArgSpec> {
vec![ArgSpec::optional("file", ArgKind::Rest, "File to write")]
}
fn names(&self) -> &[&'static str] {
&["w", "write"]
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl CommandHandler for WriteCommand {
fn execute(&self, runtime: &mut SessionRuntime<'_>, ctx: &CommandContext) -> CommandResult {
let Some(buffer_id) = ctx.buffer_id() else {
return CommandResult::Error("no buffer".to_string());
};
let explicit_file = ctx.string("file");
let path = if let Some(file) = explicit_file {
file.to_string()
} else if let Some(existing) = runtime.buffer_file_path(buffer_id) {
existing
} else {
return CommandResult::Error("No file name".to_string());
};
#[allow(clippy::cast_possible_truncation)]
runtime.kernel().event_bus.emit(BufferWillSave {
buffer_id: buffer_id.as_usize() as u64,
path: path.clone(),
});
let Some(content) = runtime.buffer_content(buffer_id) else {
return CommandResult::Error("buffer not found".to_string());
};
let Some(vfs) = ctx.vfs() else {
return CommandResult::Error("VFS not available".to_string());
};
if let Err(e) = encode_and_write(runtime, &path, &content, vfs.as_ref()) {
return CommandResult::Error(format!("Write failed: {e}"));
}
if explicit_file.is_some() {
runtime.rename_buffer(buffer_id, &path);
}
runtime.set_buffer_modified(buffer_id, false);
#[allow(clippy::cast_possible_truncation)]
let buffer_id_raw = buffer_id.as_usize() as u64;
runtime.kernel().event_bus.emit(BufferSaved {
buffer_id: buffer_id_raw,
path,
});
CommandResult::Success
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn encode_and_write(
runtime: &mut SessionRuntime<'_>,
path: &str,
content: &str,
vfs: &dyn reovim_driver_vfs::VfsDriver,
) -> Result<(), String> {
if let Some(buffer_id) = runtime.active_buffer() {
let codec_state = runtime.shared_ext_mut::<CodecSessionState>();
if let Some(metadata) = codec_state.and_then(|cs| cs.get(buffer_id)) {
if metadata.get("readonly") == Some("true") {
return Err("buffer is read-only (lossy codec decode)".to_string());
}
let content_type = metadata.content_type().clone();
let metadata_clone = metadata.clone();
let services = &runtime.kernel().services;
if let Some(factory_store) = services.get::<ContentCodecFactoryStore>()
&& let Some(codec) = factory_store.find(&content_type)
{
match codec.encode(content, &metadata_clone) {
Some(Ok(bytes)) => {
return vfs
.write(Path::new(path), &bytes)
.map_err(|e| e.to_string());
}
Some(Err(e)) => {
return Err(format!("codec encode failed: {e}"));
}
None => {
return Err("buffer is read-only (one-way codec)".to_string());
}
}
}
}
}
vfs.write_str(Path::new(path), content)
.map_err(|e| e.to_string())
}
#[derive(Debug, Clone, Copy)]
pub struct WriteQuitCommand;
impl Command for WriteQuitCommand {
fn id(&self) -> CommandId {
CommandId::new(COMMANDS_MODULE, "write-quit")
}
fn description(&self) -> &'static str {
"Write the current buffer and quit the editor."
}
fn names(&self) -> &[&'static str] {
&["wq"]
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl CommandHandler for WriteQuitCommand {
fn execute(&self, runtime: &mut SessionRuntime<'_>, ctx: &CommandContext) -> CommandResult {
let result = runtime.execute_command(WRITE_CMD_ID, ctx.clone());
if result.is_error() {
return result;
}
runtime.signal(RuntimeSignal::Quit);
CommandResult::Success
}
}
#[cfg(test)]
#[path = "write_tests.rs"]
mod tests;