use crate::commands::{Command, CommandResult};
use crate::services::GitService;
use crate::app::{AppState, Action, reducer};
use crate::errors::CommandError;
use tracing::instrument;
pub struct OpenInEditorCommand {
pub path: String,
pub line: Option<usize>,
}
impl Command for OpenInEditorCommand {
#[instrument(skip(self, _git, state), fields(path = %self.path))]
fn execute(
&self,
_git: &GitService,
state: &AppState,
) -> Result<CommandResult, CommandError> {
let mut new_state = state.clone();
if self.path.is_empty() {
new_state = reducer(new_state, Action::SetStatusError(Some("No file selected".into())));
return Ok(CommandResult::StateUpdate(new_state));
}
let file_path = if self.path.starts_with('/') {
std::path::PathBuf::from(&self.path)
} else {
std::path::PathBuf::from(&state.repo_path).join(&self.path)
};
if !file_path.exists() {
new_state = reducer(new_state, Action::SetStatusError(Some(
format!("File not found: {}", file_path.display())
)));
return Ok(CommandResult::StateUpdate(new_state));
}
let editor_cmd = std::env::var("EDITOR")
.ok()
.unwrap_or_else(|| "vi".to_string());
let mut cmd = std::process::Command::new(&editor_cmd);
if let Some(line) = self.line {
if editor_cmd.contains("code") || editor_cmd.contains("vscode") {
cmd.arg("--goto").arg(format!("{}:{}", file_path.display(), line));
} else if editor_cmd.contains("vim") || editor_cmd.contains("vi") || editor_cmd.contains("nvim") {
cmd.arg(format!("+{}", line));
} else if editor_cmd.contains("nano") {
cmd.arg(format!("+{}", line));
} else if editor_cmd.contains("emacs") {
cmd.arg(format!("+{}", line));
} else {
cmd.arg(&file_path);
}
} else {
cmd.arg(&file_path);
}
match cmd.status() {
Ok(status) => {
if !status.success() {
let fallback_editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
let fallback_status = std::process::Command::new(&fallback_editor)
.arg(&file_path)
.status();
if let Ok(fallback_status) = fallback_status {
if !fallback_status.success() {
new_state = reducer(new_state, Action::SetStatusError(Some(
format!("Editor exited with code {}", fallback_status.code().unwrap_or(-1))
)));
}
} else {
new_state = reducer(new_state, Action::SetStatusError(Some(
format!("Failed to open editor: {}", editor_cmd)
)));
}
}
}
Err(e) => {
let fallback_editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
let fallback_result = std::process::Command::new(&fallback_editor)
.arg(&file_path)
.status();
match fallback_result {
Ok(_) => {
}
Err(_) => {
new_state = reducer(new_state, Action::SetStatusError(Some(
format!("Failed to open editor '{}': {}. Tried fallback '{}' but it also failed.",
editor_cmd, e, fallback_editor)
)));
}
}
}
}
Ok(CommandResult::StateUpdate(new_state))
}
}