open_editor/
editor_call_builder.rs1use std::{
2 env::{self, temp_dir},
3 path::{Path, PathBuf},
4 process::{Command, Stdio},
5};
6
7use crate::{ENV_VARS, editor::Editor, editor_kind::EditorKind, errors::OpenEditorError};
8
9pub struct EditorCallBuilder {
10 editor: Option<Editor>,
11 file_path: Option<PathBuf>,
12 custom_env_vars: Vec<String>,
13 wait: bool,
14 line_number: usize,
15 column_number: usize,
16}
17impl Default for EditorCallBuilder {
18 fn default() -> Self {
19 Self {
20 editor: None,
21 file_path: None,
22 custom_env_vars: vec![],
23 wait: true,
24 line_number: 1,
25 column_number: 1,
26 }
27 }
28}
29
30impl EditorCallBuilder {
31 #[must_use]
39 pub fn new() -> Self {
40 Self::default()
41 }
42 #[must_use]
43 pub fn at_line(self, line: usize) -> Self {
45 Self {
46 line_number: line,
47 ..self
48 }
49 }
50 #[must_use]
51 pub fn at_column(self, line: usize) -> Self {
53 Self {
54 column_number: line,
55 ..self
56 }
57 }
58 #[must_use]
60 pub fn wait_for_editor(self, value: bool) -> Self {
61 Self {
62 wait: value,
63 ..self
64 }
65 }
66 #[must_use]
69 pub fn with_env_vars(self, env_vars: &[&str]) -> Self {
70 let mut custom_env_vars = self.custom_env_vars;
71 custom_env_vars.extend(env_vars.iter().map(|&s| s.to_string()));
72 Self {
73 custom_env_vars,
74 ..self
75 }
76 }
77 pub fn open_editor(&self) -> Result<String, OpenEditorError> {
83 self.edit_string("")
84 }
85 pub fn edit_string_mut(&self, string: &mut String) -> Result<(), OpenEditorError> {
92 *string = self.edit_string(string)?;
93 Ok(())
94 }
95
96 pub fn edit_string(&self, string: &str) -> Result<String, OpenEditorError> {
102 let file_path = match &self.file_path {
103 Some(path) => path,
104 None => &{
105 let mut filename = temp_dir();
106 filename.push(String::from("open_editor_tmp_file"));
107 filename
108 },
109 };
110 std::fs::write(file_path, string).map_err(OpenEditorError::FileManipulationFail)?;
112 self.open_file(file_path)?;
113 let result =
114 std::fs::read_to_string(file_path).map_err(OpenEditorError::FileManipulationFail)?;
115
116 std::fs::remove_file(file_path).map_err(|_| {
118 OpenEditorError::TempFileCleanupFail(file_path.to_string_lossy().into_owned())
119 })?;
120
121 Ok(result)
122 }
123 pub fn open_file(&self, file_path: &Path) -> Result<(), OpenEditorError> {
128 let editor = match &self.editor {
129 Some(editor) => editor,
130 None => &self.get_default_editor()?,
131 };
132
133 let editor_call = EditorCall {
135 editor: editor.clone(),
136 file_path: file_path.to_path_buf(),
137 wait: self.wait,
138 line_number: self.line_number,
139 column_number: self.column_number,
140 };
141 editor_call.call()
142 }
143 fn get_default_editor(&self) -> Result<Editor, OpenEditorError> {
145 self.custom_env_vars
146 .clone()
147 .into_iter()
148 .chain(ENV_VARS.iter().map(|&s| s.to_string()))
149 .filter_map(env::var_os)
150 .filter(|var| !var.is_empty())
151 .map(|v| {
152 let path = Editor::get_full_path(v.clone());
153 (v.into_string().ok(), path)
154 })
155 .filter_map(|(v, path)| v.map(|v| (v, path)))
156 .map(|(v, cmd)| (Editor::new(EditorKind::from(v), cmd)))
157 .next()
158 .ok_or(OpenEditorError::NoEditorFound)
159 }
160}
161struct EditorCall {
163 editor: Editor,
164 file_path: PathBuf,
165 wait: bool,
166 line_number: usize,
167 column_number: usize,
168}
169impl EditorCall {
170 pub fn call(&self) -> Result<(), OpenEditorError> {
175 self.editor.validate_executable()?; let command = Command::new(&self.editor.binary_path)
177 .args(self.editor.editor_type.get_editor_args(
178 &self.file_path,
179 self.wait,
180 self.line_number,
181 self.column_number,
182 ))
183 .stdin(Stdio::inherit())
184 .stdout(Stdio::inherit())
185 .stderr(Stdio::inherit())
186 .spawn();
187
188 if !self.wait {
189 return Ok(());
190 }
191
192 match command {
193 Ok(output) => {
194 let output = output
195 .wait_with_output()
196 .map_err(|e| OpenEditorError::CommandFail { error: e })?;
197 if output.status.success() {
198 Ok(())
199 } else {
200 let stderr = String::from_utf8_lossy(&output.stderr);
201 Err(OpenEditorError::EditorCallError {
202 exit_code: output.status.code(),
203 stderr: stderr.to_string(),
204 })
205 }
206 }
207 Err(e) => Err(OpenEditorError::CommandFail { error: e }),
208 }
209 }
210}