1use std::{
2 fs, io,
3 path::{Path, PathBuf},
4};
5
6use crate::pattern::Pattern;
7use crate::utils::line_indent;
8
9#[derive(Debug, Clone)]
22pub struct Editor {
23 path: PathBuf,
24 buf: String,
25 dirty: bool,
26}
27
28impl Editor {
29 pub fn create<P: AsRef<Path>>(path: P) -> io::Result<Self> {
33 fs::write(&path, "")?;
34 Self::open(path)
35 }
36
37 pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
39 let p = path.as_ref().to_owned();
40 let buf = fs::read_to_string(&p)?;
41 Ok(Self {
42 path: p,
43 buf,
44 dirty: false,
45 })
46 }
47
48 pub fn rename<P: AsRef<Path>>(&mut self, new_name: P) -> io::Result<&mut Self> {
50 fs::rename(&self.path, &new_name)?;
51 self.path = new_name.as_ref().to_owned();
52 Ok(self)
53 }
54
55 pub fn save(&mut self) -> io::Result<&mut Self> {
59 if self.dirty {
60 fs::write(&self.path, &self.buf)?;
61 self.dirty = false;
62 }
63 Ok(self)
64 }
65
66 pub fn prepend(&mut self, text: &str) -> &mut Self {
68 self.buf.insert_str(0, text);
69 self.dirty = true;
70 self
71 }
72
73 pub fn append(&mut self, text: &str) -> &mut Self {
75 self.buf.push_str(text);
76 self.dirty = true;
77 self
78 }
79
80 pub fn insert_before(&mut self, marker: &str, text: &str, same_indent: bool) -> &mut Self {
85 if let Some(pos) = self.buf.find(marker) {
86 let insertion = if same_indent {
87 format!("{}{}", line_indent(&self.buf, pos), text)
88 } else {
89 text.to_owned()
90 };
91 self.buf.insert_str(pos, &insertion);
92 self.dirty = true;
93 }
94 self
95 }
96
97 pub fn insert_after(&mut self, marker: &str, text: &str, same_indent: bool) -> &mut Self {
104 if let Some(pos) = self.buf.find(marker) {
105 let after_marker = pos + marker.len();
106 let insert_pos = if self.buf[after_marker..].starts_with('\n') {
107 after_marker + 1 } else {
109 after_marker };
111
112 let mut insertion = text.to_owned();
113
114 if insert_pos == after_marker
116 && !insertion.starts_with(char::is_whitespace)
117 && !self.buf[insert_pos..].starts_with(char::is_whitespace)
118 {
119 insertion.insert(0, ' ');
120 }
121
122 if same_indent && insertion.contains('\n') {
124 let indent = line_indent(&self.buf, pos);
125 insertion = insertion
126 .split('\n')
127 .enumerate()
128 .map(|(i, line)| {
129 if i == 0 {
130 line.to_owned()
131 } else {
132 format!("{indent}{line}")
133 }
134 })
135 .collect::<Vec<_>>()
136 .join("\n");
137 }
138
139 self.buf.insert_str(insert_pos, &insertion);
140 self.dirty = true;
141 }
142 self
143 }
144
145 pub fn replace_marker(&mut self, marker: &str, text: &str, same_indent: bool) -> &mut Self {
150 if let Some(pos) = self.buf.find(marker) {
151 let indent = if same_indent {
152 line_indent(&self.buf, pos)
153 } else {
154 String::new()
155 };
156 self.buf = self.buf.replacen(marker, &(indent + text), 1);
157 self.dirty = true;
158 }
159 self
160 }
161
162 pub fn find_lines<'a, P>(&self, pattern: P, limit: Option<usize>) -> Vec<usize>
166 where
167 P: Into<Pattern<'a>>,
168 {
169 let pat = pattern.into();
170 self.buf
171 .lines()
172 .enumerate()
173 .filter(|(_, line)| pat.is_match(line))
174 .map(|(i, _)| i + 1)
175 .take(limit.unwrap_or(usize::MAX))
176 .collect()
177 }
178
179 pub fn erase<'a, P>(&mut self, pattern: P) -> &mut Self
181 where
182 P: Into<Pattern<'a>>,
183 {
184 let pat = pattern.into();
185 self.buf = pat.replace_all(&self.buf, "");
186 self.dirty = true;
187 self
188 }
189
190 pub fn replace<'a, P>(&mut self, pattern: P, replacement: &str) -> &mut Self
192 where
193 P: Into<Pattern<'a>>,
194 {
195 let pat = pattern.into();
196 self.buf = pat.replace_all(&self.buf, replacement);
197 self.dirty = true;
198 self
199 }
200
201 pub fn mask<'a, P>(&mut self, pattern: P, mask: &str) -> &mut Self
203 where
204 P: Into<Pattern<'a>>,
205 {
206 self.replace(pattern, mask)
207 }
208}