1use std::{fs, path::PathBuf};
15
16use gapbuf::GapBuffer;
17
18use crate::{
19 cache::load_cache,
20 cfg::{IterCfg, PrintCfg},
21 context, form,
22 mode::Cursors,
23 text::{Text, err},
24 ui::{Area, PushSpecs, Ui},
25 widgets::{Widget, WidgetCfg},
26};
27
28#[derive(Default, Clone)]
30pub struct FileCfg {
31 text_op: TextOp,
32 cfg: PrintCfg,
33}
34
35impl FileCfg {
36 pub(crate) fn new() -> Self {
38 FileCfg {
39 text_op: TextOp::NewBuffer,
40 cfg: PrintCfg::default_for_input(),
41 }
42 }
43
44 pub(crate) fn open_path(self, path: PathBuf) -> Self {
46 Self { text_op: TextOp::OpenPath(path), ..self }
47 }
48
49 pub(crate) fn take_from_prev(
51 self,
52 buf: GapBuffer<u8>,
53 path_kind: PathKind,
54 has_unsaved_changes: bool,
55 ) -> Self {
56 Self {
57 text_op: TextOp::TakeBuf(buf, path_kind, has_unsaved_changes),
58 ..self
59 }
60 }
61
62 pub(crate) fn set_print_cfg(&mut self, cfg: PrintCfg) {
64 self.cfg = cfg;
65 }
66}
67
68impl<U: Ui> WidgetCfg<U> for FileCfg {
69 type Widget = File;
70
71 fn build(self, _: bool) -> (Self::Widget, impl Fn() -> bool, PushSpecs) {
72 let (text, path) = match self.text_op {
73 TextOp::NewBuffer => (Text::new_with_history(), PathKind::new_unset()),
74 TextOp::TakeBuf(buf, path, has_unsaved_changes) => match &path {
75 PathKind::SetExists(p) | PathKind::SetAbsent(p) => {
76 let cursors = load_cache(p).unwrap_or_default();
77 (Text::from_file(buf, cursors, p, has_unsaved_changes), path)
78 }
79 PathKind::NotSet(_) => (Text::from_buf(buf, Some(Cursors::default()), true), path),
80 },
81 TextOp::OpenPath(path) => {
82 let canon_path = path.canonicalize();
83 if let Ok(path) = &canon_path
84 && let Ok(file) = std::fs::read_to_string(path)
85 {
86 let cursors = load_cache(path).unwrap_or_default();
87 let buf = GapBuffer::from_iter(file.bytes());
88 (
89 Text::from_file(buf, cursors, path, false),
90 PathKind::SetExists(path.clone()),
91 )
92 } else if canon_path.is_err()
93 && let Ok(mut canon_path) = path.with_file_name(".").canonicalize()
94 {
95 canon_path.push(path.file_name().unwrap());
96 (Text::new_with_history(), PathKind::SetAbsent(canon_path))
97 } else {
98 (Text::new_with_history(), PathKind::new_unset())
99 }
100 }
101 };
102
103 #[cfg(feature = "wack")]
104 let text = {
105 let mut text = text;
106 use crate::{
107 form::{self, Form},
108 text::{Key, Tag, text},
109 };
110
111 let key = Key::new();
112 let key2 = Key::new();
113 let form1 = form::set("form1lmao", Form::red().bold());
114 text.insert_tag(3, Tag::PushForm(form1), key);
115 text.insert_tag(2, Tag::PushForm(form1), key);
116 text.insert_tag(2, Tag::PushForm(form1), key2);
117
118 text.insert_tag(7, Tag::PopForm(form1), key);
119 text.insert_tag(8, Tag::PopForm(form1), key);
120 text.insert_tag(8, Tag::PopForm(form1), key2);
121
122 text
123 };
124
125 let file = File {
126 path,
127 text,
128 cfg: self.cfg,
129 printed_lines: Vec::new(),
130 layout_ordering: 0,
131 };
132
133 (file, Box::new(|| false), PushSpecs::above())
135 }
136}
137
138pub struct File {
140 path: PathKind,
141 text: Text,
142 cfg: PrintCfg,
143 printed_lines: Vec<(usize, bool)>,
144 pub(crate) layout_ordering: usize,
145}
146
147impl File {
148 #[allow(clippy::result_large_err)]
154 pub fn write(&mut self) -> Result<usize, Text> {
155 if let PathKind::SetExists(path) | PathKind::SetAbsent(path) = &self.path {
156 let path = path.clone();
157 self.text
158 .write_to(std::io::BufWriter::new(fs::File::create(&path)?))
159 .inspect(|_| self.path = PathKind::SetExists(path))
160 .map_err(Text::from)
161 } else {
162 Err(err!("No file was set"))
163 }
164 }
165
166 pub fn write_to(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<usize> {
170 self.text
171 .write_to(std::io::BufWriter::new(fs::File::create(path)?))
172 }
173
174 pub fn path(&self) -> String {
180 self.path.path()
181 }
182
183 pub fn path_set(&self) -> Option<String> {
187 self.path.path_set()
188 }
189
190 pub fn name(&self) -> String {
194 self.path.name()
195 }
196
197 pub fn name_set(&self) -> Option<String> {
201 self.path.name_set()
202 }
203
204 pub fn path_kind(&self) -> PathKind {
205 self.path.clone()
206 }
207
208 pub fn printed_lines(&self) -> &[(usize, bool)] {
214 &self.printed_lines
215 }
216
217 pub fn len_bytes(&self) -> usize {
221 self.text.len().byte()
222 }
223
224 pub fn len_chars(&self) -> usize {
226 self.text.len().char()
227 }
228
229 pub fn len_lines(&self) -> usize {
231 self.text.len().line()
232 }
233
234 pub fn text(&self) -> &Text {
236 &self.text
237 }
238
239 pub fn text_mut(&mut self) -> &mut Text {
240 &mut self.text
241 }
242
243 pub fn print_cfg(&self) -> PrintCfg {
245 self.cfg
246 }
247
248 pub fn cursors(&self) -> Option<&Cursors> {
250 self.text.cursors()
251 }
252
253 pub fn cursors_mut(&mut self) -> Option<&mut Cursors> {
255 self.text.cursors_mut()
256 }
257
258 pub fn exists(&self) -> bool {
260 self.path_set()
261 .is_some_and(|p| std::fs::exists(PathBuf::from(&p)).is_ok_and(|e| e))
262 }
263}
264
265impl<U: Ui> Widget<U> for File {
266 type Cfg = FileCfg;
267
268 fn cfg() -> Self::Cfg {
269 FileCfg::new()
270 }
271
272 fn update(&mut self, _area: &U::Area) {}
273
274 fn text(&self) -> &Text {
275 &self.text
276 }
277
278 fn text_mut(&mut self) -> &mut Text {
279 self.text_mut()
280 }
281
282 fn print_cfg(&self) -> PrintCfg {
283 self.cfg
284 }
285
286 fn print(&mut self, area: &<U as Ui>::Area) {
287 let (start, _) = area.first_points(&self.text, self.cfg);
288
289 let mut last_line = area
290 .rev_print_iter(self.text.iter_rev(start), IterCfg::new(self.cfg))
291 .find_map(|(caret, item)| caret.wrap.then_some(item.line()));
292
293 self.printed_lines.clear();
294 let printed_lines = &mut self.printed_lines;
295
296 let mut has_wrapped = false;
297
298 area.print_with(
299 &mut self.text,
300 self.cfg,
301 form::painter(),
302 move |caret, item| {
303 has_wrapped |= caret.wrap;
304 if has_wrapped && item.part.is_char() {
305 has_wrapped = false;
306 let line = item.line();
307 let wrapped = last_line.is_some_and(|ll| ll == line);
308 last_line = Some(line);
309 printed_lines.push((line, wrapped));
310 }
311 },
312 )
313 }
314
315 fn once() -> crate::Result<(), ()> {
316 Ok(())
317 }
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
322pub enum PathKind {
323 SetExists(PathBuf),
324 SetAbsent(PathBuf),
325 NotSet(usize),
326}
327
328impl PathKind {
329 fn new_unset() -> PathKind {
331 use std::sync::atomic::{AtomicUsize, Ordering};
332 static UNSET_COUNT: AtomicUsize = AtomicUsize::new(1);
333
334 PathKind::NotSet(UNSET_COUNT.fetch_add(1, Ordering::Relaxed))
335 }
336
337 pub fn path(&self) -> String {
338 match self {
339 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
340 path.to_string_lossy().to_string()
341 }
342 PathKind::NotSet(id) => {
343 let path = std::env::current_dir()
344 .unwrap()
345 .to_string_lossy()
346 .to_string();
347
348 format!("{path}/*scratch file*#{id}")
349 }
350 }
351 }
352
353 pub fn path_set(&self) -> Option<String> {
354 match self {
355 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
356 Some(path.to_string_lossy().to_string())
357 }
358 PathKind::NotSet(_) => None,
359 }
360 }
361
362 pub fn name(&self) -> String {
363 match self {
364 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
365 let cur_dir = context::cur_dir();
366 if let Ok(path) = path.strip_prefix(cur_dir) {
367 path.to_string_lossy().to_string()
368 } else {
369 path.to_string_lossy().to_string()
370 }
371 }
372 PathKind::NotSet(id) => format!("*scratch file #{id}*"),
373 }
374 }
375
376 pub fn name_set(&self) -> Option<String> {
377 match self {
378 PathKind::SetExists(path) | PathKind::SetAbsent(path) => {
379 let cur_dir = context::cur_dir();
380 Some(if let Ok(path) = path.strip_prefix(cur_dir) {
381 path.to_string_lossy().to_string()
382 } else {
383 path.to_string_lossy().to_string()
384 })
385 }
386 PathKind::NotSet(_) => None,
387 }
388 }
389}
390
391#[derive(Default, Clone)]
393enum TextOp {
394 #[default]
395 NewBuffer,
396 TakeBuf(GapBuffer<u8>, PathKind, bool),
397 OpenPath(PathBuf),
398}