use std::io::{self, Read, Write};
use crate::action::{Action, neovim::NeovimAction};
use crate::hook::{self, Hook, HookEvent, HookOutput, PermissionDecision, Tool};
use crate::utils;
pub fn handle_hook() -> anyhow::Result<()> {
let mut input = String::new();
io::stdin().read_to_string(&mut input)?;
let hook = hook::parse_hook(&input)?;
let nvim_action = get_neovim_action()?;
let output = match hook {
Hook::Tool(h) => match h.hook_event_name {
HookEvent::PreToolUse => handle_pre_tool_use(&h.tool, nvim_action.as_ref()),
HookEvent::PostToolUse => handle_post_tool_use(&h.tool, nvim_action.as_ref()),
},
Hook::UserPrompt => handle_user_prompt_submit(nvim_action.as_ref()),
};
io::stdout().write_all(output.to_json()?.as_bytes())?;
Ok(())
}
fn get_neovim_action() -> anyhow::Result<Option<NeovimAction>> {
let socket_paths = utils::find_matching_sockets()?;
Ok(if socket_paths.is_empty() {
None
} else {
Some(NeovimAction::new(socket_paths))
})
}
fn handle_pre_tool_use(tool: &Tool, nvim_action: Option<&NeovimAction>) -> HookOutput {
match tool {
Tool::Edit(file_tool) | Tool::Write(file_tool) | Tool::MultiEdit(file_tool) => {
check_buffer_modifications(nvim_action, &file_tool.file_path)
}
_ => HookOutput::new(),
}
}
fn handle_post_tool_use(tool: &Tool, nvim_action: Option<&NeovimAction>) -> HookOutput {
match tool {
Tool::Edit(file_tool) | Tool::Write(file_tool) | Tool::MultiEdit(file_tool) => {
refresh_buffer(nvim_action, &file_tool.file_path)
}
_ => HookOutput::new(),
}
}
fn handle_user_prompt_submit(nvim_action: Option<&NeovimAction>) -> HookOutput {
let Some(action) = nvim_action else {
return HookOutput::new();
};
let Ok(selections) = action.get_visual_selections() else {
return HookOutput::new();
};
if selections.is_empty() {
return HookOutput::new();
}
let context = selections
.iter()
.map(|ctx| {
format!(
"[Selected from {}:{}-{}]\n```\n{}\n```",
ctx.file_path, ctx.start_line, ctx.end_line, ctx.content
)
})
.collect::<Vec<_>>()
.join("\n\n");
HookOutput::new().with_additional_context(context)
}
fn check_buffer_modifications(nvim_action: Option<&NeovimAction>, file_path: &str) -> HookOutput {
let Some(action) = nvim_action else {
return HookOutput::new();
};
let Ok(status) = action.buffer_status(file_path) else {
return HookOutput::new();
};
if status.has_unsaved_changes && status.is_current {
if let Err(e) = action.send_message("Claude tried to edit this file") {
eprintln!("Warning: Failed to send message to Neovim: {}", e);
}
HookOutput::new().with_permission_decision(
PermissionDecision::Deny,
Some("The file is being edited by the user, try again later".to_string()),
)
} else {
HookOutput::new()
}
}
fn refresh_buffer(nvim_action: Option<&NeovimAction>, file_path: &str) -> HookOutput {
let Some(action) = nvim_action else {
return HookOutput::new();
};
if let Err(e) = action.refresh_buffer(file_path) {
eprintln!("Warning: Failed to refresh buffer: {}", e);
}
HookOutput::new()
}